Merge remote-tracking branch 'origin/develop' into develop

# Conflicts:
#	src/main/java/seng302/server/ServerThread.java
This commit is contained in:
Michael Rausch
2017-07-25 23:10:41 +12:00
89 changed files with 3616 additions and 1329 deletions
+17
View File
@@ -0,0 +1,17 @@
engines:
pmd:
enabled: true
channel: "beta"
fixme:
enabled: true
config:
strings:
- FIXME
- TODO
- BUG
- FIX
ratings:
paths:
- "**.java"
+7
View File
@@ -36,6 +36,13 @@
<version>2.7.13</version> <version>2.7.13</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.26-incubating</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
+6 -45
View File
@@ -4,76 +4,37 @@ import javafx.application.Application;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage; import javafx.stage.Stage;
import seng302.client.ClientPacketParser;
import seng302.models.PolarTable; import seng302.models.PolarTable;
import seng302.models.stream.StreamParser;
import seng302.models.stream.StreamReceiver; import seng302.models.stream.StreamReceiver;
import seng302.server.ServerThread;
public class App extends Application { public class App extends Application {
@Override @Override
public void start(Stage primaryStage) throws Exception { 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")); Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
primaryStage.setTitle("RaceVision"); primaryStage.setTitle("RaceVision");
primaryStage.setScene(new Scene(root, 1530, 960)); primaryStage.setScene(new Scene(root, 1530, 960));
primaryStage.setMaxWidth(1530); primaryStage.setMaxWidth(1530);
primaryStage.setMaxHeight(960); primaryStage.setMaxHeight(960);
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
// primaryStage.setMaximized(true); // primaryStage.setMaximized(true);
primaryStage.show(); primaryStage.show();
primaryStage.setOnCloseRequest(e -> { primaryStage.setOnCloseRequest(e -> {
StreamParser.appClose(); ClientPacketParser.appClose();
StreamReceiver.noMoreBytes(); StreamReceiver.noMoreBytes();
System.exit(0); System.exit(0);
}); });
} }
public static void main(String[] args) { 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); launch(args);
} }
} }
-63
View File
@@ -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);
}
}
@@ -1,4 +1,4 @@
package seng302.models.stream; package seng302.client;
import java.io.IOException; import java.io.IOException;
@@ -11,7 +11,6 @@ import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
@@ -23,6 +22,7 @@ import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import seng302.models.Yacht; import seng302.models.Yacht;
import seng302.models.mark.Mark; import seng302.models.mark.Mark;
import seng302.models.stream.XMLParser;
import seng302.models.stream.packets.BoatPositionPacket; import seng302.models.stream.packets.BoatPositionPacket;
import seng302.models.stream.packets.StreamPacket; 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 * that are threadsafe so the visualiser can always access the latest speed and position available
* Created by kre39 on 23/04/17. * Created by kre39 on 23/04/17.
*/ */
public class StreamParser extends Thread { public class ClientPacketParser {
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markLocations = new ConcurrentHashMap<>(); public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markLocations = new ConcurrentHashMap<>();
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatLocations = new ConcurrentHashMap<>(); public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatLocations = new ConcurrentHashMap<>();
private String threadName;
private Thread t;
private static boolean newRaceXmlReceived = false; private static boolean newRaceXmlReceived = false;
private static boolean raceStarted = false; private static boolean raceStarted = false;
private static XMLParser xmlObject; private static XMLParser xmlObject = new XMLParser();
private static boolean raceFinished = false; private static boolean raceFinished = false;
private static boolean streamStatus = false; private static boolean streamStatus = false;
private static long timeSinceStart = -1; private static long timeSinceStart = -1;
@@ -48,64 +46,26 @@ public class StreamParser extends Thread {
private static Map<Integer, Yacht> boatsPos = new ConcurrentSkipListMap<>(); private static Map<Integer, Yacht> boatsPos = new ConcurrentSkipListMap<>();
private static double windDirection = 0; private static double windDirection = 0;
private static Double windSpeed = 0d; private static Double windSpeed = 0d;
private static Long currentTimeLong; private static Long currentTimeLong;
private static String currentTimeString; private static String currentTimeString;
private static boolean appRunning; private static boolean appRunning;
private static Map<Integer, Yacht> clientStateBoats = new ConcurrentHashMap<>();
//CONVERSION CONSTANTS //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 * 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) { public ClientPacketParser() {
this.threadName = threadName;
} }
/**
* 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 * Looks at the type of the packet then sends it to the appropriate parser to extract the
* specific data associated with that packet type * specific data associated with that packet type
* *
* @param packet the packet to be looked at and processed * @param packet the packet to be looked at and processed
*/ */
private static void parsePacket(StreamPacket packet) { public static void parsePacket(StreamPacket packet) {
try { try {
switch (packet.getType()) { switch (packet.getType()) {
case HEARTBEAT: case HEARTBEAT:
@@ -145,8 +105,6 @@ public class StreamParser extends Thread {
case AVG_WIND: case AVG_WIND:
extractAvgWind(packet); extractAvgWind(packet);
break; break;
default:
break;
} }
} catch (NullPointerException e) { } catch (NullPointerException e) {
System.out.println("Error parsing packet"); System.out.println("Error parsing packet");
@@ -212,8 +170,10 @@ public class StreamParser extends Thread {
if (raceStatus == 4 || raceStatus == 8) { if (raceStatus == 4 || raceStatus == 8) {
raceFinished = true; raceFinished = true;
raceStarted = false; raceStarted = false;
ClientState.setRaceStarted(false);
} else if (!raceStarted) { } else if (!raceStarted) {
raceStarted = true; raceStarted = true;
ClientState.setRaceStarted(true);
raceFinished = false; raceFinished = false;
} }
timeSinceStart = timeTillStart; timeSinceStart = timeTillStart;
@@ -225,41 +185,40 @@ public class StreamParser extends Thread {
int noBoats = payload[22]; int noBoats = payload[22];
int raceType = payload[23]; int raceType = payload[23];
clientStateBoats = ClientState.getBoats();
for (int i = 0; i < noBoats; i++) { for (int i = 0; i < noBoats; i++) {
long boatStatusSourceID = bytesToLong( long boatStatusSourceID = bytesToLong(
Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20))); Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20)));
Yacht boat = boats.get((int) boatStatusSourceID); int boatStatus = (int) payload[28 + (i * 20)];
boat.setBoatStatus((int) payload[28 + (i * 20)]); int boatLegNumber = (int) payload[29 + (i * 20)];
setBoatLegPosition(boat, (int) payload[29 + (i * 20)]); int boatPenaltyAwarded = (int) payload[30 + (i * 20)];
boat.setPenaltiesAwarded((int) payload[30 + (i * 20)]); int boatPenaltyServed = (int) payload[31 + (i * 20)];
boat.setPenaltiesServed((int) payload[31 + (i * 20)]); long estTimeAtNextMark = bytesToLong(
Long estTimeAtNextMark = bytesToLong(
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20))); 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))); 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); boat.setEstimateTimeAtFinish(estTimeAtFinish);
// boatsPos.put(estTimeAtFinish, boat);
// String boatStatus = "SourceID: " + boatStatusSourceID; Yacht clientBoat = clientStateBoats.get((int) boatStatusSourceID);
// boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)]; clientBoat.setBoatStatus((boatStatus));
// boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)]; setBoatLegPosition(clientBoat, boatLegNumber);
// boatStatus += "\nPenaltiesAwarded: " + (int)payload[29 + (i * 20)]; clientBoat.setPenaltiesAwarded(boatPenaltyAwarded);
// boatStatus += "\nPenaltiesServed: " + (int)payload[30 + (i * 20)]; clientBoat.setPenaltiesServed(boatPenaltyServed);
// boatStatus += "\nEstTimeAtNextMark: " + bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20))); clientBoat.setEstimateTimeAtNextMark(estTimeAtNextMark);
// boatStatus += "\nEstTimeAtFinish: " + bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20))); clientBoat.setEstimateTimeAtFinish(estTimeAtFinish);
// boatStatuses.add(boatStatus); }
// 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){ 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 * @param packet Packet parsed in to use the payload
*/ */
private static void extractXmlMessage(StreamPacket packet) { private static void extractXmlMessage(StreamPacket packet) {
xmlObject = new XMLParser();
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageType = payload[9]; int messageType = payload[9];
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14)); long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
String xmlMessage = new String( String xmlMessage = new String(
@@ -328,9 +286,10 @@ public class StreamParser extends Thread {
xmlObject.constructXML(doc, messageType); xmlObject.constructXML(doc, messageType);
if (messageType == 7) { //7 is the boat XML if (messageType == 7) { //7 is the boat XML
boats = xmlObject.getBoatXML().getCompetingBoats(); boats = xmlObject.getBoatXML().getCompetingBoats();
ClientState.setBoats(xmlObject.getBoatXML().getCompetingBoats());
ClientState.setDirtyState(true);
} }
if (messageType == 6) { //6 is race info xml if (messageType == 6) { //6 is race info xml
newRaceXmlReceived = true; newRaceXmlReceived = true;
} }
} }
@@ -392,6 +351,7 @@ public class StreamParser extends Thread {
int messageType = payload[1]; int messageType = payload[1];
int length = payload[2]; int length = payload[2];
String message = new String(Arrays.copyOfRange(payload, 3, 3 + length)); 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 //Converts the double to a usable lat/lon
double lat = ((180d * (double) rawLat) / Math.pow(2, 31)); double lat = ((180d * (double) rawLat) / Math.pow(2, 31));
double lon = ((180d * (double) rawLon) / 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)); long heading = bytesToLong(Arrays.copyOfRange(payload, 28, 30));
double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0; 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 //type 1 is a racing yacht and type 3 is a mark, needed for updating positions of the mark and boat
if (deviceType == 1){ if (deviceType == 1){
Yacht boat = boats.get((int) boatId); Yacht boat = boats.get((int) boatId);
@@ -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<Integer, Yacht> 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<Integer, Yacht> 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<Integer, Yacht> boats) {
ClientState.boats = boats;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -21,9 +21,8 @@ import javafx.scene.layout.Pane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon; import javafx.scene.shape.Polygon;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import seng302.fxObjects.BoatAnnotations; import seng302.client.ClientPacketParser;
import seng302.fxObjects.BoatGroup; import seng302.fxObjects.BoatGroup;
import seng302.fxObjects.Wake;
import seng302.models.Colors; import seng302.models.Colors;
import seng302.models.Yacht; import seng302.models.Yacht;
import seng302.models.mark.GateMark; import seng302.models.mark.GateMark;
@@ -33,19 +32,12 @@ import seng302.models.mark.MarkType;
import seng302.models.mark.SingleMark; import seng302.models.mark.SingleMark;
import seng302.models.map.Boundary; import seng302.models.map.Boundary;
import seng302.models.map.CanvasMap; import seng302.models.map.CanvasMap;
import seng302.models.stream.StreamParser;
import seng302.models.stream.XMLParser; import seng302.models.stream.XMLParser;
import seng302.models.stream.XMLParser.RaceXMLObject.Limit; import seng302.models.stream.XMLParser.RaceXMLObject.Limit;
import seng302.models.stream.XMLParser.RaceXMLObject.Participant; import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
import seng302.models.stream.packets.BoatPositionPacket; import seng302.models.stream.packets.BoatPositionPacket;
import seng302.server.simulator.GeoUtility; import seng302.utilities.GeoPoint;
import seng302.server.simulator.mark.Position; import seng302.utilities.GeoUtility;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.PriorityBlockingQueue;
/** /**
* Created by ptg19 on 15/03/17. * Created by ptg19 on 15/03/17.
@@ -118,6 +110,7 @@ public class CanvasController {
// Bind canvas size to stack pane size. // Bind canvas size to stack pane size.
canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH)); canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH));
canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT)); canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT));
} }
public void initializeCanvas() { public void initializeCanvas() {
@@ -162,13 +155,13 @@ public class CanvasController {
raceViewController.updateSparkLine(); raceViewController.updateSparkLine();
} }
updateGroups(); updateGroups();
if (StreamParser.isRaceFinished()) { if (ClientPacketParser.isRaceFinished()) {
this.stop(); this.stop();
} }
lastTime = now; lastTime = now;
} }
} }
if (StreamParser.isRaceFinished()) { if (ClientPacketParser.isRaceFinished()) {
this.stop(); this.stop();
switchToFinishScreen(); switchToFinishScreen();
} }
@@ -208,8 +201,8 @@ public class CanvasController {
double bearingFromTopLeftToOrigin = Math double bearingFromTopLeftToOrigin = Math
.toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY())); .toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY()));
// the top left extreme // the top left extreme
Position topLeftPos = new Position(maxLatPoint.getLatitude(), minLonPoint.getLongitude()); GeoPoint topLeftPos = new GeoPoint(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
Position originPos = GeoUtility GeoPoint originPos = GeoUtility
.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin); .getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin);
// distance from origin corner to bottom right corner of the panel // distance from origin corner to bottom right corner of the panel
@@ -218,7 +211,7 @@ public class CanvasController {
.pow(PANEL_WIDTH * metersPerPixelX, 2)); .pow(PANEL_WIDTH * metersPerPixelX, 2));
double bearingFromOriginToBottomRight = Math double bearingFromOriginToBottomRight = Math
.toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT)); .toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT));
Position bottomRightPos = GeoUtility GeoPoint bottomRightPos = GeoUtility
.getGeoCoordinate(originPos, bearingFromOriginToBottomRight, .getGeoCoordinate(originPos, bearingFromOriginToBottomRight,
distanceFromOriginToBottomRight); distanceFromOriginToBottomRight);
@@ -237,7 +230,7 @@ public class CanvasController {
* in a compound mark etc.. * in a compound mark etc..
*/ */
private void addRaceBorder() { private void addRaceBorder() {
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML(); XMLParser.RaceXMLObject raceXMLObject = ClientPacketParser.getXmlObject().getRaceXML();
ArrayList<Limit> courseLimits = raceXMLObject.getCourseLimit(); ArrayList<Limit> courseLimits = raceXMLObject.getCourseLimit();
raceBorder.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1)); raceBorder.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1));
raceBorder.setStrokeWidth(3); raceBorder.setStrokeWidth(3);
@@ -255,7 +248,7 @@ public class CanvasController {
for (BoatGroup boatGroup : boatGroups) { for (BoatGroup boatGroup : boatGroups) {
// some raceObjects will have multiple ID's (for instance gate marks) // some raceObjects will have multiple ID's (for instance gate marks)
//checking if the current "ID" has any updates associated with it //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()) { if (boatGroup.isStopped()) {
updateBoatGroup(boatGroup); updateBoatGroup(boatGroup);
} }
@@ -264,7 +257,7 @@ public class CanvasController {
} }
for (MarkGroup markGroup : markGroups) { for (MarkGroup markGroup : markGroups) {
for (Long id : markGroup.getRaceIds()) { for (Long id : markGroup.getRaceIds()) {
if (StreamParser.markLocations.containsKey(id)) { if (ClientPacketParser.markLocations.containsKey(id)) {
updateMarkGroup(id, markGroup); updateMarkGroup(id, markGroup);
} }
} }
@@ -273,13 +266,14 @@ public class CanvasController {
} }
private void checkForCourseChanges() { private void checkForCourseChanges() {
if (StreamParser.isNewRaceXmlReceived()){ if (ClientPacketParser.isNewRaceXmlReceived()) {
addRaceBorder(); addRaceBorder();
} }
} }
private void updateBoatGroup(BoatGroup boatGroup) { private void updateBoatGroup(BoatGroup boatGroup) {
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId()); PriorityBlockingQueue<BoatPositionPacket> movementQueue = ClientPacketParser.boatLocations
.get(boatGroup.getRaceId());
// giving the movementQueue a 5 packet buffer to account for slightly out of order packets // giving the movementQueue a 5 packet buffer to account for slightly out of order packets
if (movementQueue.size() > 0) { if (movementQueue.size() > 0) {
try { try {
@@ -297,7 +291,8 @@ public class CanvasController {
} }
void updateMarkGroup (long raceId, MarkGroup markGroup) { void updateMarkGroup (long raceId, MarkGroup markGroup) {
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.markLocations.get(raceId); PriorityBlockingQueue<BoatPositionPacket> movementQueue = ClientPacketParser.markLocations
.get(raceId);
if (movementQueue.size() > 0){ if (movementQueue.size() > 0){
try { try {
BoatPositionPacket positionPacket = movementQueue.take(); BoatPositionPacket positionPacket = movementQueue.take();
@@ -313,12 +308,12 @@ public class CanvasController {
* Draws all the boats. * Draws all the boats.
*/ */
private void initializeBoats() { private void initializeBoats() {
Map<Integer, Yacht> boats = StreamParser.getBoats(); Map<Integer, Yacht> boats = ClientPacketParser.getBoats();
Group wakes = new Group(); Group wakes = new Group();
Group trails = new Group(); Group trails = new Group();
Group annotations = new Group(); Group annotations = new Group();
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML() ArrayList<Participant> participants = ClientPacketParser.getXmlObject().getRaceXML()
.getParticipants(); .getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>(); ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) { for (Participant p : participants) {
@@ -326,7 +321,7 @@ public class CanvasController {
} }
for (Yacht boat : boats.values()) { for (Yacht boat : boats.values()) {
if (participantIDs.contains(boat.getSourceID())) { if (participantIDs.contains(boat.getSourceId())) {
boat.setColour(Colors.getColor()); boat.setColour(Colors.getColor());
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour()); BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
boatGroups.add(boatGroup); boatGroups.add(boatGroup);
@@ -342,7 +337,8 @@ public class CanvasController {
} }
private void initializeMarks() { private void initializeMarks() {
List<Mark> allMarks = StreamParser.getXmlObject().getRaceXML().getNonDupCompoundMarks(); List<Mark> allMarks = ClientPacketParser.getXmlObject().getRaceXML()
.getNonDupCompoundMarks();
for (Mark mark : allMarks) { for (Mark mark : allMarks) {
if (mark.getMarkType() == MarkType.SINGLE_MARK) { if (mark.getMarkType() == MarkType.SINGLE_MARK) {
SingleMark sMark = (SingleMark) mark; SingleMark sMark = (SingleMark) mark;
@@ -408,7 +404,7 @@ public class CanvasController {
*/ */
private void fitMarksToCanvas() { private void fitMarksToCanvas() {
//Check is called once to avoid unnecessarily change the course limits once the race is running //Check is called once to avoid unnecessarily change the course limits once the race is running
StreamParser.isNewRaceXmlReceived(); ClientPacketParser.isNewRaceXmlReceived();
findMinMaxPoint(); findMinMaxPoint();
double minLonToMaxLon = scaleRaceExtremities(); double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon); calculateReferencePointLocation(minLonToMaxLon);
@@ -424,7 +420,8 @@ public class CanvasController {
*/ */
private void findMinMaxPoint() { private void findMinMaxPoint() {
List<Limit> sortedPoints = new ArrayList<>(); List<Limit> sortedPoints = new ArrayList<>();
for (Limit limit : StreamParser.getXmlObject().getRaceXML().getCourseLimit()) {
for (Limit limit : ClientPacketParser.getXmlObject().getRaceXML().getCourseLimit()) {
sortedPoints.add(limit); sortedPoints.add(limit);
} }
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat)); sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
@@ -584,4 +581,5 @@ public class CanvasController {
List<MarkGroup> getMarkGroups() { List<MarkGroup> getMarkGroups() {
return markGroups; return markGroups;
} }
} }
@@ -1,39 +1,99 @@
package seng302.controllers; 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.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ResourceBundle; 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 { public class Controller implements Initializable {
@FXML @FXML
private AnchorPane contentPane; 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 { try {
contentPane.getChildren().removeAll(); contentPane.getChildren().removeAll();
contentPane.getChildren().clear(); contentPane.getChildren().clear();
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren() FXMLLoader fxmlLoader = new FXMLLoader((getClass().getResource(jfxUrl)));
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); Parent view = fxmlLoader.load();
contentPane.getChildren().addAll(view);
return fxmlLoader.getController();
} catch (javafx.fxml.LoadException e) { } catch (javafx.fxml.LoadException e) {
System.err.println(e.getCause()); System.err.println(e.getCause());
} catch (IOException e) { } catch (IOException e) {
System.err.println(e); System.err.println(e);
} }
return null;
} }
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
setContentPane("/views/StartScreenView.fxml"); StartScreenController startScreenController = (StartScreenController) setContentPane("/views/StartScreenView.fxml");
StreamParser.boatLocations.clear(); 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;
} }
} }
@@ -15,8 +15,8 @@ import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import seng302.client.ClientPacketParser;
import seng302.models.Yacht; import seng302.models.Yacht;
import seng302.models.stream.StreamParser;
import seng302.models.stream.XMLParser.RaceXMLObject.Participant; import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
public class FinishScreenViewController implements Initializable { public class FinishScreenViewController implements Initializable {
@@ -59,7 +59,7 @@ public class FinishScreenViewController implements Initializable {
); );
// check if the boat is racing // check if the boat is racing
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML() ArrayList<Participant> participants = ClientPacketParser.getXmlObject().getRaceXML()
.getParticipants(); .getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>(); ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) { for (Participant p : participants) {
@@ -67,8 +67,8 @@ public class FinishScreenViewController implements Initializable {
} }
// add data to table // add data to table
for (Yacht boat : StreamParser.getBoatsPos().values()) { for (Yacht boat : ClientPacketParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) { if (participantIDs.contains(boat.getSourceId())) {
data.add(boat); data.add(boat);
} }
} }
@@ -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<ObservableList<String>> competitors = new ArrayList<>();
private static ObservableList<String> firstCompetitor = FXCollections.observableArrayList();
private static ObservableList<String> secondCompetitor = FXCollections.observableArrayList();
private static ObservableList<String> thirdCompetitor = FXCollections.observableArrayList();
private static ObservableList<String> fourthCompetitor = FXCollections.observableArrayList();
private static ObservableList<String> fifthCompetitor = FXCollections.observableArrayList();
private static ObservableList<String> sixthCompetitor = FXCollections.observableArrayList();
private static ObservableList<String> seventhCompetitor = FXCollections.observableArrayList();
private static ObservableList<String> eighthCompetitor = FXCollections.observableArrayList();
private ClientStateQueryingRunnable clientStateQueryingRunnable;
private static List<ImageView> imageViews;
private static List<ListView> 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<Integer> 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;
}
}
@@ -27,7 +27,8 @@ import javafx.stage.Stage;
import javafx.stage.StageStyle; import javafx.stage.StageStyle;
import javafx.util.Duration; import javafx.util.Duration;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import seng302.GeometryUtils; import seng302.client.ClientPacketParser;
import seng302.utilities.GeoUtility;
import seng302.controllers.annotations.Annotation; import seng302.controllers.annotations.Annotation;
import seng302.controllers.annotations.ImportantAnnotationController; import seng302.controllers.annotations.ImportantAnnotationController;
import seng302.controllers.annotations.ImportantAnnotationDelegate; import seng302.controllers.annotations.ImportantAnnotationDelegate;
@@ -38,7 +39,6 @@ import seng302.models.*;
import seng302.models.mark.GateMark; import seng302.models.mark.GateMark;
import seng302.models.mark.Mark; import seng302.models.mark.Mark;
import seng302.models.mark.SingleMark; import seng302.models.mark.SingleMark;
import seng302.models.stream.StreamParser;
import seng302.models.stream.XMLParser; import seng302.models.stream.XMLParser;
import java.io.IOException; import java.io.IOException;
@@ -51,6 +51,8 @@ import java.util.stream.Collectors;
*/ */
public class RaceViewController extends Thread implements ImportantAnnotationDelegate { public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
@FXML
private Text windSpeedText;
@FXML @FXML
private LineChart raceSparkLine; private LineChart raceSparkLine;
@FXML @FXML
@@ -93,7 +95,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
raceSparkLine.getYAxis().setTranslateX(-5); raceSparkLine.getYAxis().setTranslateX(-5);
raceSparkLine.getYAxis().setAutoRanging(false); raceSparkLine.getYAxis().setAutoRanging(false);
sparklineYAxis.setTickMarkVisible(false); sparklineYAxis.setTickMarkVisible(false);
startingBoats = new ArrayList<>(StreamParser.getBoats().values()); startingBoats = new ArrayList<>(ClientPacketParser.getBoats().values());
includedCanvasController.setup(this); includedCanvasController.setup(this);
includedCanvasController.initializeCanvas(); includedCanvasController.initializeCanvas();
@@ -103,6 +105,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
initialiseBoatSelectionComboBox(); initialiseBoatSelectionComboBox();
includedCanvasController.timer.start(); includedCanvasController.timer.start();
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
} }
@@ -136,7 +139,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
Scene scene = new Scene(fxmlLoader.load(), 469, 298); Scene scene = new Scene(fxmlLoader.load(), 469, 298);
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString()); scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
stage.initStyle(StageStyle.UNDECORATED); stage.initStyle(StageStyle.UNDECORATED);
stage.setScene(scene); stage.setScene(scene);
stage.show(); stage.show();
@@ -199,7 +201,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
*/ */
void updateSparkLine(){ void updateSparkLine(){
// Collect the racing boats that aren't already in the chart // Collect the racing boats that aren't already in the chart
ArrayList<Yacht> sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceID()) ArrayList<Yacht> sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceId())
&& yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new)); && yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
// Obtain the qualifying boats to set the max on the Y axis // Obtain the qualifying boats to set the max on the Y axis
@@ -212,7 +214,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
Series<String, Double> yachtData = new Series<>(); Series<String, Double> yachtData = new Series<>();
yachtData.setName(yacht.getBoatName()); yachtData.setName(yacht.getBoatName());
yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition()))); 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) // 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 * @param legNumber the leg number that the position will be assigned to
*/ */
public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){ public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){
XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID()); XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceId());
positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition()))); 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 -> { event -> {
updateRaceTime(); updateRaceTime();
updateWindDirection(); updateWindDirection();
updateOrder(); // updateOrder();
updateBoatSelectionComboBox(); updateBoatSelectionComboBox();
}) })
); );
@@ -303,7 +305,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
private Mark getNextMark(BoatGroup bg) { private Mark getNextMark(BoatGroup bg) {
Integer legNumber = bg.getBoat().getLegNumber(); Integer legNumber = bg.getBoat().getLegNumber();
List<XMLParser.RaceXMLObject.Corner> markSequence = StreamParser.getXmlObject().getRaceXML().getCompoundMarkSequence(); List<XMLParser.RaceXMLObject.Corner> markSequence = ClientPacketParser.getXmlObject()
.getRaceXML().getCompoundMarkSequence();
if (legNumber == 0) { if (legNumber == 0) {
return null; return null;
@@ -315,7 +318,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
if (legNumber + 2 == corner.getSeqID()) { if (legNumber + 2 == corner.getSeqID()) {
Integer thisCompoundMarkID = corner.getCompoundMarkID(); Integer thisCompoundMarkID = corner.getCompoundMarkID();
for (Mark mark : StreamParser.getXmlObject().getRaceXML().getAllCompoundMarks()) { for (Mark mark : ClientPacketParser.getXmlObject().getRaceXML()
.getAllCompoundMarks()) {
if (mark.getCompoundMarkID() == thisCompoundMarkID) { if (mark.getCompoundMarkID() == thisCompoundMarkID) {
return mark; 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() { private void updateWindDirection() {
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection())); windDirectionText.setText(String.format("%.1f°", ClientPacketParser.getWindDirection()));
windArrowText.setRotate(StreamParser.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 * Updates the clock for the race
*/ */
private void updateRaceTime() { private void updateRaceTime() {
if (StreamParser.isRaceFinished()) { if (ClientPacketParser.isRaceFinished()) {
timerLabel.setFill(Color.RED); timerLabel.setFill(Color.RED);
timerLabel.setText("Race Finished!"); timerLabel.setText("Race Finished!");
} else { } 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 * in the boat selection combo box
*/ */
private void updateBoatSelectionComboBox() { private void updateBoatSelectionComboBox() {
ObservableList<Yacht> observableBoats = FXCollections ObservableList<Yacht> observableBoats = FXCollections
.observableArrayList(StreamParser.getBoatsPos().values()); .observableArrayList(ClientPacketParser.getBoatsPos().values());
boatSelectionComboBox.setItems(observableBoats); 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 * section
*/ */
private void updateOrder() { private void updateOrder() {
@@ -370,16 +375,16 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// list of racing boat id // list of racing boat id
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML() ArrayList<Participant> participants = ClientPacketParser.getXmlObject().getRaceXML()
.getParticipants(); .getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>(); ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) { for (Participant p : participants) {
participantIDs.add(p.getsourceID()); participantIDs.add(p.getsourceID());
} }
if (StreamParser.isRaceStarted()) { if (ClientPacketParser.isRaceStarted()) {
for (Yacht boat : StreamParser.getBoatsPos().values()) { for (Yacht boat : ClientPacketParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing if (participantIDs.contains(boat.getSourceId())) { // check if the boat is racing
if (boat.getBoatStatus() == 3) { // 3 is finish status if (boat.getBoatStatus() == 3) { // 3 is finish status
Text textToAdd = new Text(boat.getPosition() + ". " + Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " (Finished)"); boat.getShortName() + " (Finished)");
@@ -396,8 +401,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} }
} }
} else { } else {
for (Yacht boat : StreamParser.getBoats().values()) { for (Yacht boat : ClientPacketParser.getBoats().values()) {
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing if (participantIDs.contains(boat.getSourceId())) { // check if the boat is racing
Text textToAdd = new Text(boat.getPosition() + ". " + Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " "); boat.getShortName() + " ");
textToAdd.setFill(Paint.valueOf("#d3d3d3")); textToAdd.setFill(Paint.valueOf("#d3d3d3"));
@@ -434,9 +439,11 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
Point2D markPoint2 = includedCanvasController.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude()); Point2D markPoint2 = includedCanvasController.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
HashMap<Double, Double> angleAndSpeed; HashMap<Double, Double> angleAndSpeed;
if (isUpwind) { if (isUpwind) {
angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed()); angleAndSpeed = PolarTable
.getOptimalUpwindVMG(ClientPacketParser.getWindSpeed());
} else { } else {
angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed()); angleAndSpeed = PolarTable
.getOptimalDownwindVMG(ClientPacketParser.getWindSpeed());
} }
Double resultingAngle = angleAndSpeed.keySet().iterator().next(); 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 boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
Point2D gateMidPoint = markPoint1.midpoint(markPoint2); Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
Integer lineFuncResult = GeometryUtils.lineFunction(boatCurrentPos, gateMidPoint, markPoint2); Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
Line rightLayline = new Line(); Line rightLayline = new Line();
Line leftLayline = new Line(); Line leftLayline = new Line();
if (lineFuncResult == 1) { if (lineFuncResult == 1) {
rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection()); rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle,
leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection()); ClientPacketParser
.getWindDirection());
leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle,
ClientPacketParser
.getWindDirection());
} else if (lineFuncResult == -1) { } else if (lineFuncResult == -1) {
rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection()); rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle,
leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection()); ClientPacketParser
.getWindDirection());
leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle,
ClientPacketParser
.getWindDirection());
} }
leftLayline.setStrokeWidth(0.5); leftLayline.setStrokeWidth(0.5);
@@ -548,16 +563,16 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
private String getTimeSinceStartOfRace() { private String getTimeSinceStartOfRace() {
String timerString = "0:00"; String timerString = "0:00";
if (StreamParser.getTimeSinceStart() > 0) { if (ClientPacketParser.getTimeSinceStart() > 0) {
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60); String timerMinute = Long.toString(ClientPacketParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60); String timerSecond = Long.toString(ClientPacketParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) { if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond; timerSecond = "0" + timerSecond;
} }
timerString = "-" + timerMinute + ":" + timerSecond; timerString = "-" + timerMinute + ":" + timerSecond;
} else { } else {
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60); String timerMinute = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60); String timerSecond = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) { if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond; timerSecond = "0" + timerSecond;
} }
@@ -637,4 +652,5 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
public static boolean sparkLineStatus(Integer yachtId) { public static boolean sparkLineStatus(Integer yachtId) {
return sparkLineData.containsKey(yachtId); return sparkLineData.containsKey(yachtId);
} }
} }
@@ -1,191 +1,162 @@
package seng302.controllers; package seng302.controllers;
import java.io.IOException; import java.net.Inet4Address;
import java.net.URL; import java.net.NetworkInterface;
import java.util.ArrayList; import java.util.Enumeration;
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 javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.scene.control.Alert;
import javafx.scene.control.Button; import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Label; import javafx.scene.control.TextField;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.paint.Color; import seng302.client.ClientState;
import seng302.models.Yacht; import seng302.client.ClientToServerThread;
import seng302.models.stream.StreamParser; import seng302.gameServer.GameState;
import seng302.models.stream.XMLParser.RaceXMLObject.Participant; 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 @FXML
private GridPane gridPane; private TextField ipTextField;
@FXML @FXML
private Label timeTillLive; private TextField portTextField;
@FXML @FXML
private Button streamButton; private GridPane startScreen2;
@FXML
private Button switchToRaceViewButton;
@FXML
private TableView<Yacht> teamList;
@FXML
private TableColumn<Yacht, String> boatNameCol;
@FXML
private TableColumn<Yacht, String> shortNameCol;
@FXML
private TableColumn<Yacht, String> countryCol;
@FXML
private TableColumn<Yacht, String> posCol;
@FXML
private Label realTime;
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 { try {
// get the main controller anchor pane (MainView.fxml) AnchorPane contentPane = (AnchorPane) startScreen2.getParent();
AnchorPane contentPane = (AnchorPane) gridPane.getParent();
contentPane.getChildren().removeAll(); contentPane.getChildren().removeAll();
contentPane.getChildren().clear(); contentPane.getChildren().clear();
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren() FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl));
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); contentPane.getChildren().addAll((Pane) fxmlLoader.load());
return fxmlLoader.getController();
} catch (javafx.fxml.LoadException e) { } catch (javafx.fxml.LoadException e) {
e.printStackTrace(); e.printStackTrace();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
return null;
} }
@Override
public void initialize(URL location, ResourceBundle resources) { /**
gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); * ATTEMPTS TO:
teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString()); * 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 * ATTEMPTS TO:
* second. * 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() { @FXML
// reset boolean for switch to race view public void connectButtonPressed() {
switchedToRaceView = false; // TODO: 10/07/17 wmu16 - Finish function
try {
String ipAddress = ipTextField.getText().trim().toLowerCase();
Integer port = Integer.valueOf(portTextField.getText().trim());
if (StreamParser.isStreamStatus()) { ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, port);
streamButton.setVisible(false); ClientState.setHost(false);
realTime.setVisible(true); ClientState.setConnectedToHost(true);
timeTillLive.setVisible(true);
timeTillLive.setTextFill(Color.GREEN); controller.setClientToServerThread(clientToServerThread);
timeTillLive.setText("Connecting..."); setContentPane("/views/LobbyView.fxml");
Timer timer = new Timer(); } catch (Exception e) {
timer.scheduleAtFixedRate(new TimerTask() { Alert alert = new Alert(AlertType.ERROR);
@Override alert.setHeaderText("Cannot reach the host");
public void run() { alert.setContentText("Please check your host IP address.");
Platform.runLater(() -> { alert.showAndWait();
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);
} }
} }
public void switchToRaceView() { public void setController(Controller controller) {
StreamParser.boatLocations.clear(); this.controller = controller;
switchedToRaceView = true;
setContentPane("/views/RaceView.fxml");
} }
private void updateTeamList() { /**
ObservableList<Yacht> 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<NetworkInterface> 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); Enumeration<InetAddress> addresses = ni.getInetAddresses();
while(addresses.hasMoreElements()) {
boatNameCol.setCellValueFactory( InetAddress address = addresses.nextElement();
new PropertyValueFactory<>("boatName") if(address instanceof Inet4Address) { // skip all ipv6
); ipAddress = address.getHostAddress();
shortNameCol.setCellValueFactory( }
new PropertyValueFactory<>("shortName")
);
countryCol.setCellValueFactory(
new PropertyValueFactory<>("country")
);
posCol.setCellValueFactory(
new PropertyValueFactory<>("position")
);
// check if the boat is racing
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
.getParticipants();
ArrayList<Integer> 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);
} }
} }
} 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;
} }
} }
@@ -5,8 +5,8 @@ import javafx.scene.Group;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import seng302.client.ClientPacketParser;
import seng302.models.Yacht; import seng302.models.Yacht;
import seng302.models.stream.StreamParser;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@@ -83,12 +83,12 @@ public class BoatAnnotations extends Group{
} }
void update () { 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) { if (boat.getTimeTillNext() != null) {
DateFormat format = new SimpleDateFormat("mm:ss"); DateFormat format = new SimpleDateFormat("mm:ss");
String timeToNextMark = format String timeToNextMark = format
.format(boat.getTimeTillNext() - StreamParser.getCurrentTimeLong()); .format(boat.getTimeTillNext() - ClientPacketParser.getCurrentTimeLong());
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark); estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
} else { } else {
estTimeToNextMarkObject.setText("Next mark: -"); estTimeToNextMarkObject.setText("Next mark: -");
@@ -97,7 +97,7 @@ public class BoatAnnotations extends Group{
if (boat.getMarkRoundTime() != null) { if (boat.getMarkRoundTime() != null) {
DateFormat format = new SimpleDateFormat("mm:ss"); DateFormat format = new SimpleDateFormat("mm:ss");
String elapsedTime = format String elapsedTime = format
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundTime()); .format(ClientPacketParser.getCurrentTimeLong() - boat.getMarkRoundTime());
legTimeObject.setText("Last mark: " + elapsedTime); legTimeObject.setText("Last mark: " + elapsedTime);
}else { }else {
legTimeObject.setText("Last mark: - "); legTimeObject.setText("Last mark: - ");
+29 -33
View File
@@ -1,25 +1,21 @@
package seng302.fxObjects; package seng302.fxObjects;
import java.util.ArrayList; import java.util.ArrayList;
import javafx.event.EventHandler;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.CacheHint; import javafx.scene.CacheHint;
import javafx.scene.Group; import javafx.scene.Group;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Line; import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon; import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate; import javafx.scene.transform.Rotate;
import seng302.client.ClientPacketParser;
import seng302.models.Yacht; import seng302.models.Yacht;
import seng302.GeometryUtils; import seng302.utilities.GeoUtility;
import seng302.controllers.CanvasController; import seng302.controllers.CanvasController;
import seng302.models.mark.GateMark; import seng302.models.mark.GateMark;
import seng302.models.mark.Mark; import seng302.models.mark.Mark;
import seng302.models.mark.SingleMark; 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 * 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; isStopped = true;
} }
if (distanceTravelled > 70) { // if (distanceTravelled > 70) {
distanceTravelled = 0d; // distanceTravelled = 0d;
//
if (lastPoint != null) { // if (lastPoint != null) {
Line l = new Line( // Line l = new Line(
lastPoint.getX(), // lastPoint.getX(),
lastPoint.getY(), // lastPoint.getY(),
boatPoly.getLayoutX(), // boatPoly.getLayoutX(),
boatPoly.getLayoutY() // boatPoly.getLayoutY()
); // );
l.getStrokeDashArray().setAll(3d, 7d); // l.getStrokeDashArray().setAll(3d, 7d);
l.setStroke(boat.getColour()); // l.setStroke(boat.getColour());
l.setCache(true); // l.setCache(true);
l.setCacheHint(CacheHint.SPEED); // l.setCacheHint(CacheHint.SPEED);
lineGroup.getChildren().add(l); // lineGroup.getChildren().add(l);
} // }
//
if (destinationSet) { // if (destinationSet) {
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); // lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
} // }
} // }
wake.updatePosition(); wake.updatePosition();
} }
@@ -242,7 +238,7 @@ public class BoatGroup extends Group {
*/ */
public Boolean isUpwindLeg(CanvasController canvasController, Mark nextMark) { public Boolean isUpwindLeg(CanvasController canvasController, Mark nextMark) {
Double windAngle = StreamParser.getWindDirection(); Double windAngle = ClientPacketParser.getWindDirection();
GateMark thisGateMark = (GateMark) nextMark; GateMark thisGateMark = (GateMark) nextMark;
SingleMark nextMark1 = thisGateMark.getSingleMark1(); SingleMark nextMark1 = thisGateMark.getSingleMark1();
SingleMark nextMark2 = thisGateMark.getSingleMark2(); SingleMark nextMark2 = thisGateMark.getSingleMark2();
@@ -250,11 +246,11 @@ public class BoatGroup extends Group {
Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude()); Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); 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 boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
Integer windLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint); 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. * @return An array containing all ID's associated with this RaceObject.
*/ */
public long getRaceId() { public long getRaceId() {
return boat.getSourceID(); return boat.getSourceId();
} }
public Group getWake () { public Group getWake () {
@@ -12,7 +12,6 @@ import seng302.models.mark.GateMark;
import seng302.models.mark.Mark; import seng302.models.mark.Mark;
import seng302.models.mark.MarkType; import seng302.models.mark.MarkType;
import seng302.models.mark.SingleMark; import seng302.models.mark.SingleMark;
import seng302.GeometryUtils;
/** /**
* Grouping of javaFX objects needed to represent a Mark on screen. * Grouping of javaFX objects needed to represent a Mark on screen.
@@ -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);
}
@@ -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;
}
}
@@ -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<Player> players;
private static Map<Integer, Yacht> 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<Player> 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<Integer, Yacht> 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;
}
}
@@ -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<Player> 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);
}
}
@@ -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<ServerToClientThread> serverToClientThreads = new ArrayList<>();
private PriorityBlockingQueue<StreamPacket> 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();
}
}
}
@@ -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();
}
}
}
@@ -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;
}
}
@@ -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<Yacht> 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<BoatSubMessage> 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;
}
}
+72
View File
@@ -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();
}
}
+40 -18
View File
@@ -1,6 +1,10 @@
package seng302.models; 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.ArrayList;
import java.util.HashMap; 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. * 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 * 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 * 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<>(); polarTable = new HashMap<>();
upwindOptimal = new HashMap<>(); upwindOptimal = new HashMap<>();
downwindOptimal = new HashMap<>(); downwindOptimal = new HashMap<>();
@@ -34,7 +37,7 @@ public final class PolarTable {
String line; String line;
Boolean isHeaderLine = true; Boolean isHeaderLine = true;
try (BufferedReader br = new BufferedReader(new FileReader(file))) { try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) {
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
String[] thisLine = line.split(","); String[] thisLine = line.split(",");
@@ -69,6 +72,8 @@ public final class PolarTable {
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@@ -122,7 +127,7 @@ public final class PolarTable {
*/ */
public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) { public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) {
Double polarWindSpeed = getClosestMatch(thisWindSpeed); Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
return upwindOptimal.get(polarWindSpeed); return upwindOptimal.get(polarWindSpeed);
} }
@@ -134,30 +139,47 @@ public final class PolarTable {
*/ */
public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) { public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) {
Double polarWindSpeed = getClosestMatch(thisWindSpeed); Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
return downwindOptimal.get(polarWindSpeed); return downwindOptimal.get(polarWindSpeed);
} }
private static Double getClosestMatch(Double thisWindSpeed) { public static Double getBoatSpeed(Double thisWindSpeed, Double thisHeading) {
ArrayList<Double> windValues = new ArrayList<>(polarTable.keySet()); Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
Double polarAngle = getClosestAngleInPolar(polarTable.get(polarWindSpeed), thisHeading);
Double lowerVal = windValues.get(0); return polarTable.get(polarWindSpeed).get(polarAngle);
Double upperVal = windValues.get(1); }
for(int i = 0; i < windValues.size() - 1; i++) {
lowerVal = windValues.get(i); public static Double getClosestWindSpeedInPolar(Double thisWindSpeed) {
upperVal = windValues.get(i+1); Double smallestDif = Double.POSITIVE_INFINITY;
if (thisWindSpeed <= upperVal) { Double closestWind = 0d;
break;
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<Double, Double> 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;
} }
} }
+168 -21
View File
@@ -1,11 +1,17 @@
package seng302.models; package seng302.models;
import javafx.scene.paint.Color; import static seng302.utilities.GeoUtility.getGeoCoordinate;
import seng302.models.mark.Mark;
import seng302.controllers.RaceViewController;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; 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. * Yacht class for the racing boat.
@@ -15,15 +21,25 @@ import java.text.SimpleDateFormat;
*/ */
public class Yacht { public class Yacht {
private final Double TURN_STEP = 5.0;
private Double lastHeading;
private Boolean sailIn;
// Used in boat group // Used in boat group
private Color colour; private Color colour;
private String boatType; private String boatType;
private Integer sourceID; private Integer sourceId;
private String hullID; //matches HullNum in the XML spec. private String hullID; //matches HullNum in the XML spec.
private String shortName; private String shortName;
private String boatName; private String boatName;
private String country; private String country;
// Situational data
// Boat status // Boat status
private Integer boatStatus; private Integer boatStatus;
private Integer legNumber; private Integer legNumber;
@@ -31,7 +47,9 @@ public class Yacht {
private Integer penaltiesServed; private Integer penaltiesServed;
private Long estimateTimeAtFinish; private Long estimateTimeAtFinish;
private String position; private String position;
private double velocity; private GeoPoint location;
private Double heading;
private Double velocity;
private Long timeTillNext; private Long timeTillNext;
private Long markRoundTime; private Long markRoundTime;
@@ -40,13 +58,30 @@ public class Yacht {
private Mark nextMark; 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. * Used in EventTest and RaceTest.
* *
* @param boatName Create a yacht object with name. * @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.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.boatName = boatName;
this.velocity = boatVelocity; this.velocity = boatVelocity;
this.shortName = shortName; 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.boatType = boatType;
this.sourceID = sourceID; this.sourceId = sourceId;
this.hullID = hullID; this.hullID = hullID;
this.shortName = shortName; this.shortName = shortName;
this.boatName = boatName; this.boatName = boatName;
this.country = country; this.country = country;
this.position = "-"; 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() { public String getBoatType() {
return boatType; return boatType;
} }
public Integer getSourceID() { public Integer getSourceId() {
return sourceID; //@TODO Remove and merge with Creating Game Loop
if (sourceId == null) return 0;
return sourceId;
} }
public String getHullID() { public String getHullID() {
if (hullID == null) return "";
return hullID; return hullID;
} }
@@ -95,6 +228,7 @@ public class Yacht {
} }
public String getCountry() { public String getCountry() {
if (country == null) return "";
return country; return country;
} }
@@ -111,7 +245,8 @@ public class Yacht {
} }
public void setLegNumber(Integer legNumber) { 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); RaceViewController.updateYachtPositionSparkline(this, legNumber);
} }
this.legNumber = legNumber; this.legNumber = legNumber;
@@ -171,10 +306,14 @@ public class Yacht {
this.markRoundTime = markRoundingTime; this.markRoundTime = markRoundingTime;
} }
public double getVelocity() { public double getVelocityMMS() {
return velocity; return velocity;
} }
public Double getVelocityKnots() {
return velocity / 1000 * ClientPacketParser.MS_TO_KNOTS;
}
public Long getTimeTillNext() { public Long getTimeTillNext() {
return timeTillNext; return timeTillNext;
} }
@@ -191,17 +330,25 @@ public class Yacht {
this.lastMarkRounded = lastMarkRounded; this.lastMarkRounded = lastMarkRounded;
} }
public void setNextMark(Mark nextMark) {
this.nextMark = nextMark;
}
public Mark getNextMark(){
return nextMark;
}
public Boolean getSailIn() {
return sailIn;
}
@Override @Override
public String toString() { public String toString() {
return boatName; return boatName;
} }
public void setNextMark(Mark nextMark) { public GeoPoint getLocation() {
this.nextMark = nextMark; return location;
} }
public Mark getNextMark(){
return nextMark;
}
} }
@@ -1,6 +1,8 @@
package seng302.models.map; package seng302.models.map;
import javafx.geometry.Point2D;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import seng302.utilities.GeoPoint;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import java.net.URL; import java.net.URL;
@@ -65,10 +67,10 @@ public class CanvasMap {
private MapSize getMapSize(int zoom, Boundary boundary) { private MapSize getMapSize(int zoom, Boundary boundary) {
double scale = Math.pow(2, zoom); double scale = Math.pow(2, zoom);
MapGeo geoSW = new MapGeo(boundary.getSouthLat(), boundary.getWestLng()); GeoPoint geoSW = new GeoPoint(boundary.getSouthLat(), boundary.getWestLng());
MapGeo geoNE = new MapGeo(boundary.getNorthLat(), boundary.getEastLng()); GeoPoint geoNE = new GeoPoint(boundary.getNorthLat(), boundary.getEastLng());
MapPoint pointSW = MercatorProjection.toMapPoint(geoSW); Point2D pointSW = MercatorProjection.toMapPoint(geoSW);
MapPoint pointNE = MercatorProjection.toMapPoint(geoNE); Point2D pointNE = MercatorProjection.toMapPoint(geoNE);
return new MapSize(Math.abs(pointNE.getX() - pointSW.getX()) * scale, return new MapSize(Math.abs(pointNE.getX() - pointSW.getX()) * scale,
Math.abs(pointNE.getY() - pointSW.getY()) * scale); Math.abs(pointNE.getY() - pointSW.getY()) * scale);
} }
@@ -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;
}
}
@@ -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;
}
}
@@ -1,5 +1,8 @@
package seng302.models.map; package seng302.models.map;
import javafx.geometry.Point2D;
import seng302.utilities.GeoPoint;
/** /**
* An utility class useful to convert between Geo locations and Mercator projection * An utility class useful to convert between Geo locations and Mercator projection
* planar coordinates. * planar coordinates.
@@ -22,31 +25,31 @@ public class MercatorProjection {
/** /**
* Projects a Geo Location (lat, lng) on a planar * Projects a Geo Location (lat, lng) on a planar
* @param geo MapGeo (lat, lng) location to be projected * @param geo GeoPoint (lat, lng) location to be projected
* @return the projection GeoPoint (x, y) on planar * @return the projection Point2D (x, y) on planar
*/ */
public static MapPoint toMapPoint(MapGeo geo) { public static Point2D toMapPoint(GeoPoint geo) {
MapPoint point = new MapPoint(0, 0); double x, y;
MapPoint origin = new MapPoint(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0); Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
point.setX(origin.getX() + geo.getLng() * pixelsPerLngDegree); x = (origin.getX() + geo.getLng() * pixelsPerLngDegree);
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to // 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. // 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()))); double sinY = bound(Math.sin(Math.toRadians(geo.getLat())));
point.setY(origin.getY() + 0.5 * Math.log((1 + sinY) / (1 - sinY)) * (-pixelsPerLngRadian)); y = origin.getY() + 0.5 * Math.log((1 + sinY) / (1 - sinY)) * (-pixelsPerLngRadian);
return point; return new Point2D(x, y);
} }
/** /**
* Converts the planar projection (x, y) back to Geo Location (lat, lng) * 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 * @return the original Geo location converted from the given projection point
*/ */
public static MapGeo toMapGeo(MapPoint point) { public static GeoPoint toMapGeo(Point2D point) {
MapPoint origin = new MapPoint(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0); Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
double lng = (point.getX() - origin.getX()) / pixelsPerLngDegree; double lng = (point.getX() - origin.getX()) / pixelsPerLngDegree;
double latRadians = (point.getY() - origin.getY()) / (-pixelsPerLngRadian); double latRadians = (point.getY() - origin.getY()) / (-pixelsPerLngRadian);
double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2.0); double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2.0);
return new MapGeo(lat, lng); return new GeoPoint(lat, lng);
} }
} }
@@ -0,0 +1,7 @@
package seng302.models.stream;
import seng302.models.stream.packets.StreamPacket;
public interface PacketBufferDelegate {
boolean addToBuffer(StreamPacket streamPacket);
}
@@ -1,11 +1,15 @@
package seng302.models.stream; package seng302.models.stream;
import seng302.models.stream.packets.StreamPacket; 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.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket; import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Comparator; import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
import java.util.zip.CRC32; import java.util.zip.CRC32;
@@ -13,9 +17,10 @@ import java.util.zip.Checksum;
public class StreamReceiver extends Thread { public class StreamReceiver extends Thread {
private InputStream stream; private InputStream inputStream;
private OutputStream outputStream;
private Socket host; private Socket host;
private ByteArrayOutputStream crcBuffer; private ByteArrayOutputStream crcBuffer;
private Thread t; private Thread t;
private String threadName; private String threadName;
public static PriorityBlockingQueue<StreamPacket> packetBuffer; public static PriorityBlockingQueue<StreamPacket> packetBuffer;
@@ -58,49 +63,43 @@ public class StreamReceiver extends Thread {
public void connect(){ public void connect(){
try {
stream = host.getInputStream();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
int sync1; // int sync1;
int sync2; // int sync2;
moreBytes = true; // moreBytes = true;
while(moreBytes) { // while(moreBytes) {
try { // try {
crcBuffer = new ByteArrayOutputStream(); // crcBuffer = new ByteArrayOutputStream();
sync1 = readByte(); // sync1 = readByte();
sync2 = readByte(); // sync2 = readByte();
//checking if it is the start of the packet // //checking if it is the start of the packet
if(sync1 == 0x47 && sync2 == 0x83) { // if(sync1 == 0x47 && sync2 == 0x83) {
int type = readByte(); // int type = readByte();
//No. of milliseconds since Jan 1st 1970 // //No. of milliseconds since Jan 1st 1970
long timeStamp = bytesToLong(getBytes(6)); // long timeStamp = bytesToLong(getBytes(6));
skipBytes(4); // skipBytes(4);
long payloadLength = bytesToLong(getBytes(2)); // long payloadLength = bytesToLong(getBytes(2));
byte[] payload = getBytes((int) payloadLength); // byte[] payload = getBytes((int) payloadLength);
Checksum checksum = new CRC32(); // Checksum checksum = new CRC32();
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size()); // checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
long computedCrc = checksum.getValue(); // long computedCrc = checksum.getValue();
long packetCrc = bytesToLong(getBytes(4)); // long packetCrc = bytesToLong(getBytes(4));
if (computedCrc == packetCrc) { // if (computedCrc == packetCrc) {
packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload)); // packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload));
} else { // } else {
System.err.println("Packet has been dropped"); // System.err.println("Packet has been dropped");
} // }
} // }
} catch (Exception e) { // } catch (Exception e) {
moreBytes = false; // moreBytes = false;
} // }
} // }
} }
private int readByte() throws Exception { private int readByte() throws Exception {
int currentByte = -1; int currentByte = -1;
try { try {
currentByte = stream.read(); currentByte = inputStream.read();
crcBuffer.write(currentByte); crcBuffer.write(currentByte);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@@ -558,7 +558,7 @@ public class XMLParser {
getNodeAttributeString(currentBoat, "Country")); getNodeAttributeString(currentBoat, "Country"));
this.boats.add(boat); this.boats.add(boat);
if (boat.getBoatType().equals("Yacht")) { if (boat.getBoatType().equals("Yacht")) {
competingBoats.put(boat.getSourceID(), boat); competingBoats.put(boat.getSourceId(), boat);
} }
} }
} }
@@ -16,6 +16,7 @@ public enum PacketType {
MARK_ROUNDING, MARK_ROUNDING,
COURSE_WIND, COURSE_WIND,
AVG_WIND, AVG_WIND,
BOAT_ACTION,
OTHER; OTHER;
public static PacketType assignPacketType(int packetType){ public static PacketType assignPacketType(int packetType){
@@ -44,6 +45,8 @@ public enum PacketType {
return COURSE_WIND; return COURSE_WIND;
case 47: case 47:
return AVG_WIND; return AVG_WIND;
case 100:
return BOAT_ACTION;
default: default:
} }
return OTHER; return OTHER;
@@ -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<Yacht> 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<Yacht> 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();
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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<Integer,Boolean> boatsFinished = new HashMap<>();
private List<Boat> 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<BoatSubMessage> 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<Boat>) 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<Boat>) arg).size()) {
startSendingRaceFinishedBoatPositions();
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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<Integer, BoatActionType> 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;
}
}
@@ -1,7 +1,7 @@
package seng302.server.messages; package seng302.server.messages;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.SocketChannel; import java.io.OutputStream;
public class BoatLocationMessage extends Message { public class BoatLocationMessage extends Message {
private final int MESSAGE_SIZE = 56; private final int MESSAGE_SIZE = 56;
@@ -39,7 +39,6 @@ public class BoatLocationMessage extends Message {
* @param boatSpeed The boats speed * @param boatSpeed The boats speed
*/ */
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){ public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
boatSpeed /= 10;
messageVersionNumber = 1; messageVersionNumber = 1;
time = System.currentTimeMillis(); time = System.currentTimeMillis();
this.sourceId = sourceId; this.sourceId = sourceId;
@@ -64,6 +63,36 @@ public class BoatLocationMessage extends Message {
this.rudderAngle = 0; this.rudderAngle = 0;
setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize())); 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() { public int getSize() {
return MESSAGE_SIZE; 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());
}
} }
@@ -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;
}
}
@@ -43,10 +43,21 @@ public class Header {
buff.position(buffPos); buff.position(buffPos);
} }
/**
* Reset the buffer
*/
public void reset(){
buffPos = 0;
buff.clear();
buff.position(buffPos);
}
/** /**
* @return a ByteBuffer containing the message header * @return a ByteBuffer containing the message header
*/ */
public ByteBuffer getByteBuffer(){ public ByteBuffer getByteBuffer(){
reset();
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1); putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2); putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
@@ -1,32 +1,16 @@
package seng302.server.messages; package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.zip.CRC32;
public class Heartbeat extends Message { public class Heartbeat extends Message {
private final int MESSAGE_SIZE = 4; private final int MESSAGE_SIZE = 4;
private int seqNo;
/** /**
* Heartbeat from the AC35 Streaming data spec * Heartbeat from the AC35 Streaming data spec
* @param seqNo Increment every time a message is sent * @param seqNo Increment every time a message is sent
*/ */
public Heartbeat(int seqNo){ 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())); setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
allocateBuffer(); allocateBuffer();
@@ -36,7 +20,11 @@ public class Heartbeat extends Message {
writeCRC(); writeCRC();
rewind(); rewind();
outputStream.write(getBuffer());
} }
@Override
public int getSize() {
return MESSAGE_SIZE;
}
} }
@@ -1,10 +1,7 @@
package seng302.server.messages; package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.Channels; import java.io.OutputStream;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class MarkRoundingMessage extends Message{ public class MarkRoundingMessage extends Message{
private final long MESSAGE_VERSION_NUMBER = 1; private final long MESSAGE_VERSION_NUMBER = 1;
@@ -33,15 +30,6 @@ public class MarkRoundingMessage extends Message{
this.markId = markId; this.markId = markId;
setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize())); 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(); allocateBuffer();
writeHeaderToBuffer(); writeHeaderToBuffer();
@@ -56,7 +44,10 @@ public class MarkRoundingMessage extends Message{
writeCRC(); writeCRC();
rewind(); rewind();
}
outputStream.write(getBuffer()); @Override
public int getSize() {
return MESSAGE_SIZE;
} }
} }
@@ -1,9 +1,9 @@
package seng302.server.messages; package seng302.server.messages;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.channels.SocketChannel;
import java.util.Arrays; import java.util.Arrays;
import java.util.zip.CRC32; import java.util.zip.CRC32;
@@ -33,11 +33,6 @@ public abstract class Message {
*/ */
public abstract int getSize(); 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 * Allocate byte buffer to correct size
*/ */
@@ -45,6 +40,7 @@ public abstract class Message {
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE); buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.order(ByteOrder.LITTLE_ENDIAN);
bufferPosition = 0; 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(){ public byte[] getBuffer(){
return buffer; return buffer.array();
} }
/** /**
@@ -193,6 +189,25 @@ public abstract class Message {
return data; 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 * Reverse an array of bytes
* @param data The byte[] to reverse * @param data The byte[] to reverse
@@ -16,7 +16,8 @@ public enum MessageType {
BOAT_LOCATION(37), BOAT_LOCATION(37),
MARK_ROUNDING(38), MARK_ROUNDING(38),
COURSE_WIND(44), COURSE_WIND(44),
AVERAGE_WIND(47); AVERAGE_WIND(47),
BOAT_ACTION(100);
private int code; private int code;
@@ -1,10 +1,7 @@
package seng302.server.messages; package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.Channels; import java.io.OutputStream;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class RaceStartStatusMessage extends Message { public class RaceStartStatusMessage extends Message {
private final int MESSAGE_SIZE = 20; private final int MESSAGE_SIZE = 20;
@@ -32,15 +29,6 @@ public class RaceStartStatusMessage extends Message {
this.raceId = raceId; this.raceId = raceId;
setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize())); 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(); allocateBuffer();
writeHeaderToBuffer(); writeHeaderToBuffer();
@@ -53,7 +41,11 @@ public class RaceStartStatusMessage extends Message {
writeCRC(); writeCRC();
rewind(); rewind();
outputStream.write(getBuffer());
} }
@Override
public int getSize() {
return MESSAGE_SIZE;
}
} }
@@ -1,7 +1,7 @@
package seng302.server.messages; package seng302.server.messages;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.SocketChannel; import java.io.OutputStream;
import java.util.List; import java.util.List;
import java.util.zip.CRC32; import java.util.zip.CRC32;
@@ -9,12 +9,14 @@ public class RaceStatusMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS; private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS;
private final int MESSAGE_VERSION = 2; //Always set to 1 private final int MESSAGE_VERSION = 2; //Always set to 1
private final int MESSAGE_BASE_SIZE = 24; private final int MESSAGE_BASE_SIZE = 24;
private final double windDirFactor = 0x4000 / 90;
private long currentTime; private long currentTime;
private long raceId; private long raceId;
private RaceStatus raceStatus; private RaceStatus raceStatus;
private long expectedStartTime; private long expectedStartTime;
private WindDirection raceWindDirection; private double raceWindDirection;
private long windSpeed; private long windSpeed;
private long numBoatsInRace; private long numBoatsInRace;
private RaceType raceType; private RaceType raceType;
@@ -33,13 +35,13 @@ public class RaceStatusMessage extends Message{
* @param sourceId The source of this message * @param sourceId The source of this message
* @param boats A list of boat status sub messages * @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<BoatSubMessage> boats){ long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List<BoatSubMessage> boats){
currentTime = System.currentTimeMillis(); currentTime = System.currentTimeMillis();
this.raceId = raceId; this.raceId = raceId;
this.raceStatus = raceStatus; this.raceStatus = raceStatus;
this.expectedStartTime = expectedStartTime; this.expectedStartTime = expectedStartTime;
this.raceWindDirection = raceWindDirection; this.raceWindDirection = raceWindDirection * windDirFactor;
this.windSpeed = windSpeed; this.windSpeed = windSpeed;
this.numBoatsInRace = numBoatsInRace; this.numBoatsInRace = numBoatsInRace;
this.raceType = raceType; this.raceType = raceType;
@@ -47,22 +49,6 @@ public class RaceStatusMessage extends Message{
crc = new CRC32(); crc = new CRC32();
setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize())); 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(); allocateBuffer();
writeHeaderToBuffer(); writeHeaderToBuffer();
@@ -71,7 +57,7 @@ public class RaceStatusMessage extends Message{
putInt((int) raceId, 4); putInt((int) raceId, 4);
putByte((byte) raceStatus.getCode()); putByte((byte) raceStatus.getCode());
putInt(expectedStartTime, 6); putInt(expectedStartTime, 6);
putInt((int) raceWindDirection.getCode(), 2); putInt((int) this.raceWindDirection, 2);
putInt((int) windSpeed, 2); putInt((int) windSpeed, 2);
putByte((byte) numBoatsInRace); putByte((byte) numBoatsInRace);
putByte((byte) raceType.getCode()); putByte((byte) raceType.getCode());
@@ -82,7 +68,14 @@ public class RaceStatusMessage extends Message{
writeCRC(); writeCRC();
rewind(); rewind();
outputStream.write(getBuffer());
} }
/**
* @return the size of this message in bytes
*/
@Override
public int getSize() {
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
}
} }
@@ -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;
}
}
@@ -1,12 +1,7 @@
package seng302.server.messages; package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.zip.CRC32;
public class XMLMessage extends Message{ public class XMLMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE; private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
@@ -35,20 +30,6 @@ public class XMLMessage extends Message{
sequence = sequenceNum; sequence = sequenceNum;
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize())); 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(); allocateBuffer();
writeHeaderToBuffer(); writeHeaderToBuffer();
@@ -63,7 +44,12 @@ public class XMLMessage extends Message{
writeCRC(); writeCRC();
rewind(); rewind();
}
outputStream.write(getBuffer()); /**
* @return The length of this message
*/
public int getSize(){
return MESSAGE_SIZE + content.length();
} }
} }
@@ -1,7 +1,8 @@
package seng302.server.simulator; package seng302.server.simulator;
import seng302.server.simulator.mark.Corner; import seng302.server.simulator.mark.Corner;
import seng302.server.simulator.mark.Position; import seng302.utilities.GeoPoint;
import seng302.utilities.GeoUtility;
public class Boat { public class Boat {
@@ -29,8 +30,8 @@ public class Boat {
*/ */
public void move(double heading, double duration) { public void move(double heading, double duration) {
Double distance = speed * duration / 1000000; // convert mm to meter Double distance = speed * duration / 1000000; // convert mm to meter
Position originPos = new Position(lat, lng); GeoPoint originPos = new GeoPoint(lat, lng);
Position newPos = GeoUtility.getGeoCoordinate(originPos, heading, distance); GeoPoint newPos = GeoUtility.getGeoCoordinate(originPos, heading, distance);
this.lat = newPos.getLat(); this.lat = newPos.getLat();
this.lng = newPos.getLng(); this.lng = newPos.getLng();
} }
@@ -3,8 +3,9 @@ package seng302.server.simulator;
import seng302.server.simulator.mark.CompoundMark; import seng302.server.simulator.mark.CompoundMark;
import seng302.server.simulator.mark.Corner; import seng302.server.simulator.mark.Corner;
import seng302.server.simulator.mark.Mark; import seng302.server.simulator.mark.Mark;
import seng302.server.simulator.mark.Position;
import seng302.server.simulator.parsers.RaceParser; import seng302.server.simulator.parsers.RaceParser;
import seng302.utilities.GeoPoint;
import seng302.utilities.GeoUtility;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -80,8 +81,8 @@ public class Simulator extends Observable implements Runnable {
boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration); boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration);
Position boatPos = new Position(boat.getLat(), boat.getLng()); GeoPoint boatPos = new GeoPoint(boat.getLat(), boat.getLng());
Position lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getMark1(); GeoPoint lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getMark1();
double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos); double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos);
// if a boat passes its heading mark // 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 // move compensate distance for the mark just passed
Position pos = GeoUtility.getGeoCoordinate( GeoPoint pos = GeoUtility.getGeoCoordinate(
boat.getLastPassedCorner().getCompoundMark().getMark1(), boat.getLastPassedCorner().getCompoundMark().getMark1(),
boat.getLastPassedCorner().getBearingToNextCorner(), boat.getLastPassedCorner().getBearingToNextCorner(),
compensateDistance); compensateDistance);
boat.setLat(pos.getLat()); boat.setLat(pos.getLat());
boat.setLng(pos.getLng()); 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()); boat.getLastPassedCorner().getCompoundMark().getMark1());
} }
} }
@@ -1,10 +1,12 @@
package seng302.server.simulator.mark; package seng302.server.simulator.mark;
import seng302.utilities.GeoPoint;
/** /**
* An abstract class to represent general marks * An abstract class to represent general marks
* Created by Haoming Yin (hyi25) on 17/3/17. * Created by Haoming Yin (hyi25) on 17/3/17.
*/ */
public class Mark extends Position { public class Mark extends GeoPoint {
private int seqID; private int seqID;
private String name; private String name;
@@ -22,7 +24,7 @@ public class Mark extends Position {
*/ */
@Override @Override
public String toString() { 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() { public int getSeqID() {
@@ -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; double lat, lng;
public Position(double lat, double lng) { public GeoPoint(double lat, double lng) {
this.lat = lat; this.lat = lat;
this.lng = lng; this.lng = lng;
} }
public String toString() {
return String.format("Position at lat:%f lng:%f.", lat, lng);
}
public double getLat() { public double getLat() {
return lat; return lat;
} }
@@ -1,6 +1,6 @@
package seng302.server.simulator; package seng302.utilities;
import seng302.server.simulator.mark.Position; import javafx.geometry.Point2D;
public class GeoUtility { public class GeoUtility {
@@ -13,7 +13,7 @@ public class GeoUtility {
* @param p2 second geographical position * @param p2 second geographical position
* @return the distance in meter between two points in meters * @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 dLat = Math.toRadians(p2.getLat() - p1.getLat());
double dLon = Math.toRadians(p2.getLng() - p1.getLng()); 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° * ( Baghdad) to 35°N,135°E ( Osaka), you would start on a heading of 60°
* and end up on a heading of 120° * 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()); 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 * @param distance the distance in meter, from original position to the new position
* @return 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 b = Math.toRadians(bearing); // bearing to radians
double d = distance / 1000.0; // distance to km 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.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat),
Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat)); 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);
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

+2 -2
View File
@@ -12,8 +12,8 @@
</mark> </mark>
<mark> <mark>
<name>Start2</name> <name>Start2</name>
<latitude>57.6706330</latitude> <latitude>57.6703330</latitude>
<longitude>11.8281330</longitude> <longitude>11.8271333</longitude>
<id>123</id> <id>123</id>
</mark> </mark>
</gate> </gate>
+2 -2
View File
@@ -168,7 +168,7 @@ Remove scroll bars
.ui-table *.scroll-bar:vertical *.increment-arrow, .ui-table *.scroll-bar:vertical *.increment-arrow,
.ui-table *.scroll-bar:vertical *.decrement-arrow { .ui-table *.scroll-bar:vertical *.decrement-arrow {
-fx-background-color: null; -fx-background-color: #0e6d6c;
-fx-background-radius: 0; -fx-background-radius: 0;
-fx-background-insets: 0; -fx-background-insets: 0;
-fx-padding: 0; -fx-padding: 0;
@@ -177,7 +177,7 @@ Remove scroll bars
.ui-table *.scroll-bar:vertical *.increment-button, .ui-table *.scroll-bar:vertical *.increment-button,
.ui-table *.scroll-bar:vertical *.decrement-button { .ui-table *.scroll-bar:vertical *.decrement-button {
-fx-background-color: null; -fx-background-color: #0e6d6c;
-fx-background-radius: 0; -fx-background-radius: 0;
-fx-background-insets: 0; -fx-background-insets: 0;
-fx-padding: 0; -fx-padding: 0;
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

+171
View File
@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2015-08-28T17:32:59+0100</Modified>
<Version>12</Version>
<Snapshot>219</Snapshot>
<Settings>
<RaceBoatType Type="AC45"/>
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="53.796"/>
<ZoneLimits Limit1="200" Limit2="100" Limit3="53.796" Limit4="0" Limit5="-100"/>
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="3" Y="25" X="0"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="14">
<Vertices>
<Vtx Seq="1" Y="0" X="-1"/>
<Vtx Seq="2" Y="0.75" X="-1"/>
<Vtx Seq="3" Y="0.75" X="-0.25"/>
<Vtx Seq="4" Y="3.5" X="-0.25"/>
<Vtx Seq="5" Y="4.5" X="-1"/>
<Vtx Seq="6" Y="6.5" X="-1"/>
<Vtx Seq="7" Y="7" X="-0.5"/>
<Vtx Seq="8" Y="7" X="0.5"/>
<Vtx Seq="9" Y="6.5" X="1"/>
<Vtx Seq="10" Y="4.5" X="1"/>
<Vtx Seq="11" Y="3.5" X="0.25"/>
<Vtx Seq="12" Y="0.75" X="0.25"/>
<Vtx Seq="13" Y="0.75" X="1"/>
<Vtx Seq="14" Y="0" X="1"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="15">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46"/>
<Vtx Seq="2" Y="13.449" X="-3.46"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="13.449" X="3.46"/>
<Vtx Seq="5" Y="0" X="3.46"/>
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752"/>
<Vtx Seq="2" Y="0" X="-2.813"/>
<Vtx Seq="3" Y="0" X="-3.34"/>
<Vtx Seq="4" Y="5.351" X="-3.46"/>
<Vtx Seq="5" Y="10.544" X="-3.387"/>
<Vtx Seq="6" Y="13.449" X="-3.075"/>
<Vtx Seq="7" Y="10.851" X="-2.793"/>
<Vtx Seq="8" Y="6.669" X="-2.699"/>
<Vtx Seq="9" Y="6.669" X="2.699"/>
<Vtx Seq="10" Y="10.851" X="2.793"/>
<Vtx Seq="11" Y="13.449" X="3.075"/>
<Vtx Seq="12" Y="10.544" X="3.387"/>
<Vtx Seq="13" Y="5.351" X="3.46"/>
<Vtx Seq="14" Y="0" X="3.34"/>
<Vtx Seq="15" Y="0" X="2.813"/>
<Vtx Seq="16" Y="1.769" X="2.752"/>
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2"/>
<Vtx Seq="2" Y="11.377" X="-0.2"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="11.377" X="0.2"/>
<Vtx Seq="5" Y="6.669" X="0.2"/>
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699"/>
<Vtx Seq="2" Y="6.438" X="-2.699"/>
<Vtx Seq="3" Y="6.438" X="2.699"/>
<Vtx Seq="4" Y="2" X="2.699"/>
</Trampoline>
</BoatShape>
<BoatShape ShapeID="18">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.04"/>
<Vtx Seq="2" Y="0.11" X="-1.18"/>
<Vtx Seq="3" Y="0.42" X="-1.28"/>
<Vtx Seq="4" Y="3.74" X="-1.29"/>
<Vtx Seq="5" Y="5.36" X="-1.21"/>
<Vtx Seq="6" Y="6.29" X="-1.08"/>
<Vtx Seq="7" Y="7.15" X="-0.84"/>
<Vtx Seq="8" Y="7.63" X="-0.62"/>
<Vtx Seq="9" Y="7.94" X="-0.34"/>
<Vtx Seq="10" Y="8.06" X="0"/>
<Vtx Seq="11" Y="7.94" X="0.34"/>
<Vtx Seq="12" Y="7.63" X="0.62"/>
<Vtx Seq="13" Y="7.15" X="0.84"/>
<Vtx Seq="14" Y="6.29" X="1.08"/>
<Vtx Seq="15" Y="5.36" X="1.21"/>
<Vtx Seq="16" Y="3.74" X="1.29"/>
<Vtx Seq="17" Y="0.42" X="1.28"/>
<Vtx Seq="18" Y="0.11" X="1.18"/>
<Vtx Seq="19" Y="0" X="1.04"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="24">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.5"/>
<Vtx Seq="2" Y="7" X="-2.5"/>
<Vtx Seq="3" Y="12.6" X="-2.2"/>
<Vtx Seq="4" Y="12.6" X="2.2"/>
<Vtx Seq="5" Y="7" X="2.5"/>
<Vtx Seq="6" Y="0" X="2.5"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="34">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.16"/>
<Vtx Seq="2" Y="5.51" X="-1.16"/>
<Vtx Seq="3" Y="5.846" X="-0.84"/>
<Vtx Seq="4" Y="5.846" X="0.84"/>
<Vtx Seq="5" Y="5.51" X="1.16"/>
<Vtx Seq="6" Y="0" X="1.16"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="35">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.461"/>
<Vtx Seq="2" Y="6" X="-1.461"/>
<Vtx Seq="3" Y="7" X="-1.44"/>
<Vtx Seq="4" Y="8" X="-1.38"/>
<Vtx Seq="5" Y="9" X="-1.17"/>
<Vtx Seq="6" Y="10" X="-0.76"/>
<Vtx Seq="7" Y="10.6" X="-0.34"/>
<Vtx Seq="8" Y="10.61" X="0"/>
<Vtx Seq="9" Y="10.6" X="0.34"/>
<Vtx Seq="10" Y="10" X="0.76"/>
<Vtx Seq="11" Y="9" X="1.17"/>
<Vtx Seq="12" Y="8" X="1.38"/>
<Vtx Seq="13" Y="7" X="1.44"/>
<Vtx Seq="14" Y="6" X="1.461"/>
<Vtx Seq="15" Y="0" X="1.461"/>
</Vertices>
</BoatShape>
</BoatShapes>
<Boats>
<Boat Type="Yacht" SourceID="201" ShapeID="15" StoweName="USA" ShortName="ORACLE" ShorterName="USA" BoatName="ORACLE TEAM USA" HullNum="AC4515" Skipper="SPITHILL" Helmsman="SPITHILL" Country="USA" PeliID="101" RadioIP="172.20.2.101">
<GPSposition Z="1.78" Y="-0.331" X="-0.006"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="301" ShapeID="15" StoweName="SWE" ShortName="ARTEMIS" ShorterName="SWE" BoatName="ARTEMIS RACING" HullNum="AC4517" Skipper="OUTTERIDGE" Helmsman="OUTTERIDGE" Country="SWE" PeliID="102" RadioIP="172.20.2.102">
<GPSposition Z="1.727" Y="-0.359" X="-0.0121"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="401" ShapeID="15" StoweName="NZL" ShortName="ETNZ" ShorterName="NZL" BoatName="EMIRATES TEAM NZ" HullNum="AC4503" Skipper="ASHBY" Helmsman="BURLING" Country="NZL" PeliID="103" RadioIP="172.20.2.103">
<GPSposition Z="1.881" Y="-0.291" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="501" ShapeID="15" StoweName="JPN" ShortName="JAPAN" ShorterName="JPN" BoatName="SOFTBANK TEAM JAPAN" HullNum="AC4504" Skipper="BARKER" Helmsman="BARKER" Country="JPN" PeliID="104" RadioIP="172.20.2.104">
<GPSposition Z="1.805" Y="-0.322" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="601" ShapeID="15" StoweName="FRA" ShortName="FRANCE" ShorterName="FRA" BoatName="GROUPAMA TEAM FRANCE" HullNum="AC4505" Skipper="CAMMAS" Helmsman="CAMMAS" Country="FRA" PeliID="105" RadioIP="172.20.2.105">
<GPSposition Z="1.863" Y="-0.3" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="701" ShapeID="15" StoweName="GBR" ShortName="GBR" ShorterName="GBR" BoatName="LAND ROVER BAR" HullNum="AC4516" Skipper="ANSLIE" Helmsman="ANSLIE" Country="GBR" PeliID="106" RadioIP="172.20.2.106">
<GPSposition Z="1.734" Y="-0.352" X="0"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
</Boats>
</BoatConfig>
+161
View File
@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2015-08-28T17:32:59+0100</Modified>
<Version>12</Version>
<Snapshot>219</Snapshot>
<Settings>
<RaceBoatType Type="AC45"/>
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="53.796"/>
<ZoneLimits Limit1="200" Limit2="100" Limit3="53.796" Limit4="0" Limit5="-100"/>
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="3" Y="25" X="0"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="14">
<Vertices>
<Vtx Seq="1" Y="0" X="-1"/>
<Vtx Seq="2" Y="0.75" X="-1"/>
<Vtx Seq="3" Y="0.75" X="-0.25"/>
<Vtx Seq="4" Y="3.5" X="-0.25"/>
<Vtx Seq="5" Y="4.5" X="-1"/>
<Vtx Seq="6" Y="6.5" X="-1"/>
<Vtx Seq="7" Y="7" X="-0.5"/>
<Vtx Seq="8" Y="7" X="0.5"/>
<Vtx Seq="9" Y="6.5" X="1"/>
<Vtx Seq="10" Y="4.5" X="1"/>
<Vtx Seq="11" Y="3.5" X="0.25"/>
<Vtx Seq="12" Y="0.75" X="0.25"/>
<Vtx Seq="13" Y="0.75" X="1"/>
<Vtx Seq="14" Y="0" X="1"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="15">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46"/>
<Vtx Seq="2" Y="13.449" X="-3.46"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="13.449" X="3.46"/>
<Vtx Seq="5" Y="0" X="3.46"/>
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752"/>
<Vtx Seq="2" Y="0" X="-2.813"/>
<Vtx Seq="3" Y="0" X="-3.34"/>
<Vtx Seq="4" Y="5.351" X="-3.46"/>
<Vtx Seq="5" Y="10.544" X="-3.387"/>
<Vtx Seq="6" Y="13.449" X="-3.075"/>
<Vtx Seq="7" Y="10.851" X="-2.793"/>
<Vtx Seq="8" Y="6.669" X="-2.699"/>
<Vtx Seq="9" Y="6.669" X="2.699"/>
<Vtx Seq="10" Y="10.851" X="2.793"/>
<Vtx Seq="11" Y="13.449" X="3.075"/>
<Vtx Seq="12" Y="10.544" X="3.387"/>
<Vtx Seq="13" Y="5.351" X="3.46"/>
<Vtx Seq="14" Y="0" X="3.34"/>
<Vtx Seq="15" Y="0" X="2.813"/>
<Vtx Seq="16" Y="1.769" X="2.752"/>
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2"/>
<Vtx Seq="2" Y="11.377" X="-0.2"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="11.377" X="0.2"/>
<Vtx Seq="5" Y="6.669" X="0.2"/>
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699"/>
<Vtx Seq="2" Y="6.438" X="-2.699"/>
<Vtx Seq="3" Y="6.438" X="2.699"/>
<Vtx Seq="4" Y="2" X="2.699"/>
</Trampoline>
</BoatShape>
<BoatShape ShapeID="18">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.04"/>
<Vtx Seq="2" Y="0.11" X="-1.18"/>
<Vtx Seq="3" Y="0.42" X="-1.28"/>
<Vtx Seq="4" Y="3.74" X="-1.29"/>
<Vtx Seq="5" Y="5.36" X="-1.21"/>
<Vtx Seq="6" Y="6.29" X="-1.08"/>
<Vtx Seq="7" Y="7.15" X="-0.84"/>
<Vtx Seq="8" Y="7.63" X="-0.62"/>
<Vtx Seq="9" Y="7.94" X="-0.34"/>
<Vtx Seq="10" Y="8.06" X="0"/>
<Vtx Seq="11" Y="7.94" X="0.34"/>
<Vtx Seq="12" Y="7.63" X="0.62"/>
<Vtx Seq="13" Y="7.15" X="0.84"/>
<Vtx Seq="14" Y="6.29" X="1.08"/>
<Vtx Seq="15" Y="5.36" X="1.21"/>
<Vtx Seq="16" Y="3.74" X="1.29"/>
<Vtx Seq="17" Y="0.42" X="1.28"/>
<Vtx Seq="18" Y="0.11" X="1.18"/>
<Vtx Seq="19" Y="0" X="1.04"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="24">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.5"/>
<Vtx Seq="2" Y="7" X="-2.5"/>
<Vtx Seq="3" Y="12.6" X="-2.2"/>
<Vtx Seq="4" Y="12.6" X="2.2"/>
<Vtx Seq="5" Y="7" X="2.5"/>
<Vtx Seq="6" Y="0" X="2.5"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="34">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.16"/>
<Vtx Seq="2" Y="5.51" X="-1.16"/>
<Vtx Seq="3" Y="5.846" X="-0.84"/>
<Vtx Seq="4" Y="5.846" X="0.84"/>
<Vtx Seq="5" Y="5.51" X="1.16"/>
<Vtx Seq="6" Y="0" X="1.16"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="35">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.461"/>
<Vtx Seq="2" Y="6" X="-1.461"/>
<Vtx Seq="3" Y="7" X="-1.44"/>
<Vtx Seq="4" Y="8" X="-1.38"/>
<Vtx Seq="5" Y="9" X="-1.17"/>
<Vtx Seq="6" Y="10" X="-0.76"/>
<Vtx Seq="7" Y="10.6" X="-0.34"/>
<Vtx Seq="8" Y="10.61" X="0"/>
<Vtx Seq="9" Y="10.6" X="0.34"/>
<Vtx Seq="10" Y="10" X="0.76"/>
<Vtx Seq="11" Y="9" X="1.17"/>
<Vtx Seq="12" Y="8" X="1.38"/>
<Vtx Seq="13" Y="7" X="1.44"/>
<Vtx Seq="14" Y="6" X="1.461"/>
<Vtx Seq="15" Y="0" X="1.461"/>
</Vertices>
</BoatShape>
</BoatShapes>
<Boats>
<Boat Type="Yacht" SourceID="201" ShapeID="15" StoweName="USA" ShortName="ORACLE" ShorterName="USA" BoatName="ORACLE TEAM USA" HullNum="AC4515" Skipper="SPITHILL" Helmsman="SPITHILL" Country="USA" PeliID="101" RadioIP="172.20.2.101">
<GPSposition Z="1.78" Y="-0.331" X="-0.006"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="401" ShapeID="15" StoweName="NZL" ShortName="ETNZ" ShorterName="NZL" BoatName="EMIRATES TEAM NZ" HullNum="AC4503" Skipper="ASHBY" Helmsman="BURLING" Country="NZL" PeliID="103" RadioIP="172.20.2.103">
<GPSposition Z="1.881" Y="-0.291" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="501" ShapeID="15" StoweName="JPN" ShortName="JAPAN" ShorterName="JPN" BoatName="SOFTBANK TEAM JAPAN" HullNum="AC4504" Skipper="BARKER" Helmsman="BARKER" Country="JPN" PeliID="104" RadioIP="172.20.2.104">
<GPSposition Z="1.805" Y="-0.322" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="701" ShapeID="15" StoweName="GBR" ShortName="GBR" ShorterName="GBR" BoatName="LAND ROVER BAR" HullNum="AC4516" Skipper="ANSLIE" Helmsman="ANSLIE" Country="GBR" PeliID="106" RadioIP="172.20.2.106">
<GPSposition Z="1.734" Y="-0.352" X="0"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
</Boats>
</BoatConfig>
+171
View File
@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2015-08-28T17:32:59+0100</Modified>
<Version>12</Version>
<Snapshot>219</Snapshot>
<Settings>
<RaceBoatType Type="AC45"/>
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="53.796"/>
<ZoneLimits Limit1="200" Limit2="100" Limit3="53.796" Limit4="0" Limit5="-100"/>
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="3" Y="25" X="0"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="14">
<Vertices>
<Vtx Seq="1" Y="0" X="-1"/>
<Vtx Seq="2" Y="0.75" X="-1"/>
<Vtx Seq="3" Y="0.75" X="-0.25"/>
<Vtx Seq="4" Y="3.5" X="-0.25"/>
<Vtx Seq="5" Y="4.5" X="-1"/>
<Vtx Seq="6" Y="6.5" X="-1"/>
<Vtx Seq="7" Y="7" X="-0.5"/>
<Vtx Seq="8" Y="7" X="0.5"/>
<Vtx Seq="9" Y="6.5" X="1"/>
<Vtx Seq="10" Y="4.5" X="1"/>
<Vtx Seq="11" Y="3.5" X="0.25"/>
<Vtx Seq="12" Y="0.75" X="0.25"/>
<Vtx Seq="13" Y="0.75" X="1"/>
<Vtx Seq="14" Y="0" X="1"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="15">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46"/>
<Vtx Seq="2" Y="13.449" X="-3.46"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="13.449" X="3.46"/>
<Vtx Seq="5" Y="0" X="3.46"/>
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752"/>
<Vtx Seq="2" Y="0" X="-2.813"/>
<Vtx Seq="3" Y="0" X="-3.34"/>
<Vtx Seq="4" Y="5.351" X="-3.46"/>
<Vtx Seq="5" Y="10.544" X="-3.387"/>
<Vtx Seq="6" Y="13.449" X="-3.075"/>
<Vtx Seq="7" Y="10.851" X="-2.793"/>
<Vtx Seq="8" Y="6.669" X="-2.699"/>
<Vtx Seq="9" Y="6.669" X="2.699"/>
<Vtx Seq="10" Y="10.851" X="2.793"/>
<Vtx Seq="11" Y="13.449" X="3.075"/>
<Vtx Seq="12" Y="10.544" X="3.387"/>
<Vtx Seq="13" Y="5.351" X="3.46"/>
<Vtx Seq="14" Y="0" X="3.34"/>
<Vtx Seq="15" Y="0" X="2.813"/>
<Vtx Seq="16" Y="1.769" X="2.752"/>
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2"/>
<Vtx Seq="2" Y="11.377" X="-0.2"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="11.377" X="0.2"/>
<Vtx Seq="5" Y="6.669" X="0.2"/>
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699"/>
<Vtx Seq="2" Y="6.438" X="-2.699"/>
<Vtx Seq="3" Y="6.438" X="2.699"/>
<Vtx Seq="4" Y="2" X="2.699"/>
</Trampoline>
</BoatShape>
<BoatShape ShapeID="18">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.04"/>
<Vtx Seq="2" Y="0.11" X="-1.18"/>
<Vtx Seq="3" Y="0.42" X="-1.28"/>
<Vtx Seq="4" Y="3.74" X="-1.29"/>
<Vtx Seq="5" Y="5.36" X="-1.21"/>
<Vtx Seq="6" Y="6.29" X="-1.08"/>
<Vtx Seq="7" Y="7.15" X="-0.84"/>
<Vtx Seq="8" Y="7.63" X="-0.62"/>
<Vtx Seq="9" Y="7.94" X="-0.34"/>
<Vtx Seq="10" Y="8.06" X="0"/>
<Vtx Seq="11" Y="7.94" X="0.34"/>
<Vtx Seq="12" Y="7.63" X="0.62"/>
<Vtx Seq="13" Y="7.15" X="0.84"/>
<Vtx Seq="14" Y="6.29" X="1.08"/>
<Vtx Seq="15" Y="5.36" X="1.21"/>
<Vtx Seq="16" Y="3.74" X="1.29"/>
<Vtx Seq="17" Y="0.42" X="1.28"/>
<Vtx Seq="18" Y="0.11" X="1.18"/>
<Vtx Seq="19" Y="0" X="1.04"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="24">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.5"/>
<Vtx Seq="2" Y="7" X="-2.5"/>
<Vtx Seq="3" Y="12.6" X="-2.2"/>
<Vtx Seq="4" Y="12.6" X="2.2"/>
<Vtx Seq="5" Y="7" X="2.5"/>
<Vtx Seq="6" Y="0" X="2.5"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="34">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.16"/>
<Vtx Seq="2" Y="5.51" X="-1.16"/>
<Vtx Seq="3" Y="5.846" X="-0.84"/>
<Vtx Seq="4" Y="5.846" X="0.84"/>
<Vtx Seq="5" Y="5.51" X="1.16"/>
<Vtx Seq="6" Y="0" X="1.16"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="35">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.461"/>
<Vtx Seq="2" Y="6" X="-1.461"/>
<Vtx Seq="3" Y="7" X="-1.44"/>
<Vtx Seq="4" Y="8" X="-1.38"/>
<Vtx Seq="5" Y="9" X="-1.17"/>
<Vtx Seq="6" Y="10" X="-0.76"/>
<Vtx Seq="7" Y="10.6" X="-0.34"/>
<Vtx Seq="8" Y="10.61" X="0"/>
<Vtx Seq="9" Y="10.6" X="0.34"/>
<Vtx Seq="10" Y="10" X="0.76"/>
<Vtx Seq="11" Y="9" X="1.17"/>
<Vtx Seq="12" Y="8" X="1.38"/>
<Vtx Seq="13" Y="7" X="1.44"/>
<Vtx Seq="14" Y="6" X="1.461"/>
<Vtx Seq="15" Y="0" X="1.461"/>
</Vertices>
</BoatShape>
</BoatShapes>
<Boats>
<Boat Type="Yacht" SourceID="201" ShapeID="15" StoweName="USA" ShortName="ORACLE" ShorterName="USA" BoatName="ORACLE TEAM USA" HullNum="AC4515" Skipper="SPITHILL" Helmsman="SPITHILL" Country="USA" PeliID="101" RadioIP="172.20.2.101">
<GPSposition Z="1.78" Y="-0.331" X="-0.006"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="301" ShapeID="15" StoweName="SWE" ShortName="ARTEMIS" ShorterName="SWE" BoatName="ARTEMIS RACING" HullNum="AC4517" Skipper="OUTTERIDGE" Helmsman="OUTTERIDGE" Country="SWE" PeliID="102" RadioIP="172.20.2.102">
<GPSposition Z="1.727" Y="-0.359" X="-0.0121"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="401" ShapeID="15" StoweName="NZL" ShortName="ETNZ" ShorterName="NZL" BoatName="EMIRATES TEAM NZ" HullNum="AC4503" Skipper="ASHBY" Helmsman="BURLING" Country="NZL" PeliID="103" RadioIP="172.20.2.103">
<GPSposition Z="1.881" Y="-0.291" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="501" ShapeID="15" StoweName="JPN" ShortName="JAPAN" ShorterName="JPN" BoatName="SOFTBANK TEAM JAPAN" HullNum="AC4504" Skipper="BARKER" Helmsman="BARKER" Country="JPN" PeliID="104" RadioIP="172.20.2.104">
<GPSposition Z="1.805" Y="-0.322" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="601" ShapeID="15" StoweName="FRA" ShortName="FRANCE" ShorterName="FRA" BoatName="GROUPAMA TEAM FRANCE" HullNum="AC4505" Skipper="CAMMAS" Helmsman="CAMMAS" Country="FRA" PeliID="105" RadioIP="172.20.2.105">
<GPSposition Z="1.863" Y="-0.3" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="701" ShapeID="15" StoweName="GBR" ShortName="GBR" ShorterName="GBR" BoatName="LAND ROVER BAR" HullNum="AC4516" Skipper="ANSLIE" Helmsman="ANSLIE" Country="GBR" PeliID="106" RadioIP="172.20.2.106">
<GPSposition Z="1.734" Y="-0.352" X="0"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
</Boats>
</BoatConfig>
@@ -0,0 +1,22 @@
<BoatConfig>
<Modified>2012-05-17T07:49:40+0200</Modified>
<Version>12</Version>
<Settings>
<RaceBoatType Type="AC45" />
<BoatDimension BoatLength="14.019" HullLength="13.449" />
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="40.347" />
<ZoneLimits Limit1="200" Limit2="100" Limit3="40.347" Limit4="0" Limit5="-100" />
</Settings>
<BoatShapes>
<#-- Not used -->
</BoatShapes>
<Boats>
<#list boats as boat>
<Boat Type="Yacht" SourceID="${boat.sourceId}" ShapeID="4" HullNum="${boat.hullID}" StoweName="${boat.shortName}" ShortName="${boat.shortName}"
BoatName="${boat.boatName}" Country="${boat.country}">
<GPSposition Z="0" Y="3.7" X="0" />
<MastTop Z="0" Y="6.2" X="0" />
</Boat>
</#list>
</Boats>
</BoatConfig>
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<Race>
<CreationTimeDate>${raceStartTime}</CreationTimeDate>
<RaceStartTime Start="${raceStartTime}" Postpone="False" />
<RaceID>15082901</RaceID>
<RaceType>Fleet</RaceType>
<Participants>
<#list boats as boat>
<Yacht SourceID="${boat.sourceId}"/>
</#list>
</Participants>
<Course>
<CompoundMark CompoundMarkID="1" Name="Mark0">
<Mark SeqID="1" Name="Start Line 1" TargetLat="57.670603" TargetLng="11.828262" SourceID="122" />
<Mark SeqID="2" Name="Start Line 2" TargetLat="57.669445" TargetLng="11.826413" SourceID="123" />
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="Mark1">
<Mark SeqID="1" Name="Mark1" TargetLat="57.6675700" TargetLng="11.8359880" SourceID="131" />
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="Mark2">
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.671629" TargetLng="11.840951" SourceID="125" />
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="Mark3">
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.665316" TargetLng="11.827184" SourceID="126" />
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.664190" TargetLng="11.829576" SourceID="127" />
</CompoundMark>
<CompoundMark CompoundMarkID="5" Name="Mark2">
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
</CompoundMark>
<CompoundMark CompoundMarkID="6" Name="Mark3">
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
</CompoundMark>
<CompoundMark CompoundMarkID="7" Name="Mark2">
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
</CompoundMark>
<CompoundMark CompoundMarkID="8" Name="Mark3">
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
</CompoundMark>
<CompoundMark CompoundMarkID="9" Name="Mark2">
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
</CompoundMark>
<CompoundMark CompoundMarkID="10" Name="Mark3">
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
</CompoundMark>
<CompoundMark CompoundMarkID="11" Name="Mark4">
<Mark SeqID="1" Name="Finish Line 1" TargetLat="57.672350" TargetLng="11.842535" SourceID="128" />
<Mark SeqID="2" Name="Finish Line 2" TargetLat="57.6715240" TargetLng="11.8444950" SourceID="129" />
</CompoundMark>
</Course>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="PS" ZoneSize="3" />
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3" />
<Corner SeqID="3" CompoundMarkID="3" Rounding="SP" ZoneSize="3" />
<Corner SeqID="4" CompoundMarkID="4" Rounding="PS" ZoneSize="3" />
<Corner SeqID="5" CompoundMarkID="5" Rounding="SP" ZoneSize="3" />
<Corner SeqID="6" CompoundMarkID="6" Rounding="PS" ZoneSize="3" />
<Corner SeqID="7" CompoundMarkID="7" Rounding="SP" ZoneSize="3" />
<Corner SeqID="8" CompoundMarkID="8" Rounding="PS" ZoneSize="3" />
<Corner SeqID="9" CompoundMarkID="9" Rounding="SP" ZoneSize="3" />
<Corner SeqID="10" CompoundMarkID="10" Rounding="PS" ZoneSize="3" />
<Corner SeqID="11" CompoundMarkID="11" Rounding="PS" ZoneSize="3" />
</CompoundMarkSequence>
<CourseLimit>
<Limit SeqID="1" Lat="57.6739450" Lon="11.8417100" />
<Limit SeqID="2" Lat="57.6709520" Lon="11.8485010" />
<Limit SeqID="3" Lat="57.6690260" Lon="11.8472790" />
<Limit SeqID="4" Lat="57.6693140" Lon="11.8457610" />
<Limit SeqID="5" Lat="57.6665370" Lon="11.8432910" />
<Limit SeqID="6" Lat="57.6641400" Lon="11.8385840" />
<Limit SeqID="7" Lat="57.6629430" Lon="11.8332030" />
<Limit SeqID="8" Lat="57.6629480" Lon="11.8249660" />
<Limit SeqID="9" Lat="57.6686890" Lon="11.8250920" />
<Limit SeqID="10" Lat="57.6692230" Lon="11.8231430" />
<Limit SeqID="11" Lat="57.6725370" Lon="11.8272480" />
<Limit SeqID="12" Lat="57.6708220" Lon="11.8321340" />
</CourseLimit>
</Race>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<RegattaConfig>
<RegattaID>${regattaId}</RegattaID>
<RegattaName>${name}</RegattaName>
<CourseName>${courseName}</CourseName>
<CentralLatitude>${latitude}</CentralLatitude>
<CentralLongitude>${longitude}</CentralLongitude>
<CentralAltitude>${altitude}</CentralAltitude>
<UtcOffset>${utcOffset}</UtcOffset>
<MagneticVariation>${magneticVariation}</MagneticVariation>
</RegattaConfig>
+137
View File
@@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.*?>
<?import javafx.geometry.*?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<GridPane fx:id="lobbyScreen" nodeOrientation="LEFT_TO_RIGHT" prefHeight="960.0" prefWidth="1530.0" style="-fx-background-color: #2C2c36;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.LobbyController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="171.0" minHeight="0.0" prefHeight="31.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="462.0" minHeight="10.0" prefHeight="462.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="100.0" minHeight="0.0" prefHeight="0.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Text fx:id="lobbyIpText" fill="WHITE" strokeType="OUTSIDE" strokeWidth="0.0" text="Lobby: IP" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER">
<font>
<Font size="29.0" />
</font>
</Text>
<GridPane prefHeight="166.0" prefWidth="1530.0" GridPane.rowIndex="2">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button fx:id="readyButton" focusTraversable="false" mnemonicParsing="false" onAction="#readyButtonPressed" prefWidth="101.0" text="Ready" GridPane.halignment="CENTER" />
<Button focusTraversable="false" mnemonicParsing="false" onAction="#leaveLobbyButtonPressed" text="Leave Lobby" GridPane.columnIndex="1" GridPane.halignment="CENTER" />
</children>
</GridPane>
<GridPane GridPane.rowIndex="1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<ListView fx:id="firstListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin>
</ListView>
<ListView fx:id="secondListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin>
</ListView>
<ListView fx:id="thirdListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="3">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin></ListView>
<ListView fx:id="fourthListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="4">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin>
</ListView>
<ListView fx:id="eighthListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="4" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin></ListView>
<ListView fx:id="fifthListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin></ListView>
<ListView fx:id="sixthListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin></ListView>
<ListView fx:id="seventhListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="3" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin></ListView>
<ImageView fx:id="firstImageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="10.0" />
</GridPane.margin>
</ImageView>
<ImageView fx:id="secondImageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="10.0" />
</GridPane.margin>
</ImageView>
<ImageView fx:id="thirdImageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="10.0" />
</GridPane.margin>
</ImageView>
<ImageView fx:id="fourthImageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="10.0" />
</GridPane.margin>
</ImageView>
<ImageView fx:id="fifthImageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="10.0" />
</GridPane.margin>
</ImageView>
<ImageView fx:id="sixthImageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="10.0" />
</GridPane.margin>
</ImageView>
<ImageView fx:id="seventhImageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="10.0" />
</GridPane.margin>
</ImageView>
<ImageView fx:id="eighthImageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="10.0" />
</GridPane.margin>
</ImageView>
</children>
</GridPane>
</children>
</GridPane>
+2 -1
View File
@@ -7,4 +7,5 @@
<?import java.lang.*?> <?import java.lang.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<AnchorPane fx:id="contentPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller" />
<AnchorPane fx:id="contentPane" cache="true" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onKeyPressed="#keyPressed" onKeyReleased="#keyReleased" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller" />
+8 -3
View File
@@ -35,7 +35,12 @@
<Font name="System Bold" size="13.0" /> <Font name="System Bold" size="13.0" />
</font> </font>
</Text> </Text>
<CheckBox fx:id="toggleFps" graphicTextGap="0.0" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" styleClass="ui-checkbox" text="Show FPS" textFill="WHITE" /> <Text fx:id="windSpeedText" fill="#d3d3d3" layoutX="12.0" layoutY="213.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0 Knot" textAlignment="RIGHT">
<font>
<Font name="System Bold" size="13.0" />
</font>
</Text>
<CheckBox fx:id="toggleFps" focusTraversable="false" graphicTextGap="0.0" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" styleClass="ui-checkbox" text="Show FPS" textFill="WHITE" />
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" styleClass="text-white" /> <VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" styleClass="text-white" />
<Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0"> <Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0">
<children> <children>
@@ -48,9 +53,9 @@
</Pane> </Pane>
<Slider fx:id="annotationSlider" blockIncrement="1.0" layoutX="38.0" layoutY="527.0" majorTickUnit="1.0" max="2.0" minorTickCount="0" prefHeight="51.0" prefWidth="170.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" styleClass="ui-slider" /> <Slider fx:id="annotationSlider" blockIncrement="1.0" layoutX="38.0" layoutY="527.0" majorTickUnit="1.0" max="2.0" minorTickCount="0" prefHeight="51.0" prefWidth="170.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" styleClass="ui-slider" />
<Label layoutX="10.0" layoutY="499.0" text="Annotations" textFill="WHITE" /> <Label layoutX="10.0" layoutY="499.0" text="Annotations" textFill="WHITE" />
<Button fx:id="selectAnnotationBtn" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" /> <Button fx:id="selectAnnotationBtn" focusTraversable="false" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" />
<Text fill="WHITE" layoutX="11.0" layoutY="649.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Boat Selection" /> <Text fill="WHITE" layoutX="11.0" layoutY="649.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Boat Selection" />
<ComboBox fx:id="boatSelectionComboBox" layoutX="37.0" layoutY="664.0" prefHeight="25.0" prefWidth="170.0" promptText="Select Boat" styleClass="combo-box-base" /> <ComboBox fx:id="boatSelectionComboBox" focusTraversable="false" layoutX="37.0" layoutY="664.0" prefHeight="25.0" prefWidth="170.0" promptText="Select Boat" styleClass="combo-box-base" />
<LineChart fx:id="raceSparkLine" layoutX="-1.0" layoutY="719.0" legendVisible="false" prefHeight="277.0" prefWidth="246.0" title="Boat Positions"> <LineChart fx:id="raceSparkLine" layoutX="-1.0" layoutY="719.0" legendVisible="false" prefHeight="277.0" prefWidth="246.0" title="Boat Positions">
<xAxis> <xAxis>
<CategoryAxis label="Leg Number" side="BOTTOM" styleClass="spark-line-xaxis" /> <CategoryAxis label="Leg Number" side="BOTTOM" styleClass="spark-line-xaxis" />
+42 -43
View File
@@ -1,60 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?> <?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.canvas.*?>
<?import java.lang.*?> <?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<GridPane fx:id="gridPane" nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" style="-fx-background-color: #2C2c36;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.StartScreenController"> <GridPane fx:id="startScreen2" nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" style="-fx-background-color: #2C2c36;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.StartScreenController">
<columnConstraints> <columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" /> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="442.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="358.0" />
</columnConstraints> </columnConstraints>
<rowConstraints> <rowConstraints>
<RowConstraints percentHeight="10.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="52.0" minHeight="52.0" prefHeight="52.0" vgrow="SOMETIMES" /> <RowConstraints vgrow="SOMETIMES" />
<RowConstraints maxHeight="0.0" percentHeight="8.0" prefHeight="0.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="72.0" prefHeight="72.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="28.0" vgrow="SOMETIMES" /> <RowConstraints maxHeight="65.0" minHeight="36.0" prefHeight="46.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="55.0" minHeight="55.0" percentHeight="9.0" prefHeight="55.0" vgrow="SOMETIMES" /> <RowConstraints maxHeight="108.0" minHeight="72.0" prefHeight="98.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="0.0" minHeight="0.0" percentHeight="29.0" prefHeight="0.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="72.0" prefHeight="72.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="93.0" minHeight="72.0" prefHeight="72.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="283.0" minHeight="262.0" prefHeight="283.0" vgrow="SOMETIMES" />
</rowConstraints> </rowConstraints>
<children> <children>
<Label alignment="CENTER" text="Welcome to Race Vision" textFill="WHITE" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM"> <Label alignment="CENTER" text="Welcome to Race Vision" textFill="WHITE" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<font> <font>
<Font size="40.0" /> <Font size="40.0" />
</font> </font>
</Label> </Label>
<Label text="Your live AC35 livestream" textFill="WHITE" GridPane.halignment="CENTER" GridPane.rowIndex="1"> <Button mnemonicParsing="false" onAction="#hostButtonPressed" prefHeight="25.0" prefWidth="175.0" text="Host" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="2" />
<font> <Button mnemonicParsing="false" onAction="#connectButtonPressed" prefHeight="25.0" prefWidth="147.0" text="Connect" GridPane.columnIndex="1" GridPane.rowIndex="4">
<Font size="20.0" /> <GridPane.margin>
</font> <Insets left="5.0" right="5.0" />
</Label> </GridPane.margin></Button>
<Label text="Livestream Status:" textFill="WHITE" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="BOTTOM"> <TextField fx:id="ipTextField" alignment="CENTER" maxWidth="-Infinity" prefHeight="25.0" prefWidth="148.0" promptText="Host IP" text="132.181.14." GridPane.halignment="RIGHT" GridPane.rowIndex="4">
<font> <GridPane.margin>
<Font size="28.0" /> <Insets bottom="10.0" left="5.0" right="85.0" top="10.0" />
</font> </GridPane.margin>
</Label> </TextField>
<Label fx:id="timeTillLive" text="0:00 minutes" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="4"> <Text fill="WHITE" strokeType="OUTSIDE" strokeWidth="0.0" text="OR" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="3">
<font> <font>
<Font size="27.0" /> <Font size="21.0" />
</font> </font>
</Label> </Text>
<Button fx:id="streamButton" mnemonicParsing="false" onAction="#startStream" styleClass="blue-ui-btn" text="Click to stream" GridPane.halignment="CENTER" GridPane.rowIndex="4" /> <TextField fx:id="portTextField" alignment="CENTER" maxWidth="75.0" prefHeight="25.0" prefWidth="55.0" promptText="Port" text="4942" GridPane.halignment="RIGHT" GridPane.rowIndex="4">
<Button fx:id="switchToRaceViewButton" disable="true" mnemonicParsing="false" onAction="#switchToRaceView" styleClass="blue-ui-btn" text="Watch Race" GridPane.halignment="CENTER" GridPane.rowIndex="7" GridPane.valignment="TOP" /> <opaqueInsets>
<TableView fx:id="teamList" maxWidth="661.0" prefHeight="324.0" prefWidth="629.0" styleClass="ui-table" GridPane.halignment="CENTER" GridPane.hgrow="NEVER" GridPane.rowIndex="5" GridPane.vgrow="NEVER"> <Insets />
<columns> </opaqueInsets>
<TableColumn fx:id="posCol" editable="false" maxWidth="74.0" minWidth="74.0" prefWidth="74.0" resizable="false" sortable="false" text="Position" /> <GridPane.margin>
<TableColumn fx:id="boatNameCol" editable="false" maxWidth="171.0" minWidth="171.0" prefWidth="171.0" resizable="false" sortable="false" text="Boat Name" /> <Insets left="5.0" right="5.0" />
<TableColumn fx:id="shortNameCol" editable="false" maxWidth="155.18472290039062" minWidth="107.0" prefWidth="155.18472290039062" resizable="false" sortable="false" text="Short Name" /> </GridPane.margin>
<TableColumn fx:id="countryCol" editable="false" maxWidth="258.9999694824219" minWidth="147.0" prefWidth="258.9999694824219" resizable="false" sortable="false" text="Country" /> </TextField>
</columns>
<GridPane.margin>
<Insets top="10.0" />
</GridPane.margin>
</TableView>
<Label fx:id="realTime" text="Local time" textFill="WHITE" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="BOTTOM" />
</children> </children>
</GridPane> </GridPane>
+6 -5
View File
@@ -3,6 +3,7 @@ package seng302;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import seng302.utilities.GeoUtility;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -34,9 +35,9 @@ public class TestGeoUtils {
@Test @Test
public void testLineFunction() { public void testLineFunction() {
Integer lineFunctionResult1 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint1); Integer lineFunctionResult1 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint1);
Integer lineFunctionResult2 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint2); Integer lineFunctionResult2 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint2);
Integer lineFunctionResult3 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint3); Integer lineFunctionResult3 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint3);
//Point1 and Point2 are on opposite sides //Point1 and Point2 are on opposite sides
assertEquals(Math.abs(lineFunctionResult1), Math.abs(lineFunctionResult2)); assertEquals(Math.abs(lineFunctionResult1), Math.abs(lineFunctionResult2));
@@ -50,13 +51,13 @@ public class TestGeoUtils {
public void testMakeArbitraryVectorPoint() { public void testMakeArbitraryVectorPoint() {
//Make a point (1,0) from point (0,0) //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); Point2D expected = new Point2D(1,0);
assertEquals(expected.getX(), newPoint.getX(), 1E-6); assertEquals(expected.getX(), newPoint.getX(), 1E-6);
assertEquals(expected.getY(), newPoint.getY(), 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); expected = new Point2D(0, 1);
assertEquals(expected.getX(), newPoint.getX(), 1E-6); assertEquals(expected.getX(), newPoint.getX(), 1E-6);
@@ -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<Yacht> yachts = new ArrayList<Yacht>();
@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));
}
}
@@ -1,6 +1,9 @@
package seng302.models.map; package seng302.models.map;
import org.junit.Test; import org.junit.Test;
import seng302.utilities.GeoPoint;
import java.awt.geom.Point2D;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -11,30 +14,30 @@ import static org.junit.Assert.*;
public class MercatorProjectionTest { public class MercatorProjectionTest {
@Test @Test
public void toMapPoint() throws Exception { public void toMapPoint() throws Exception {
MapGeo geo1 = new MapGeo(12.485394, 19.38947); GeoPoint geo1 = new GeoPoint(12.485394, 19.38947);
MapPoint actualPoint1 = MercatorProjection.toMapPoint(geo1); javafx.geometry.Point2D actualPoint1 = MercatorProjection.toMapPoint(geo1);
MapPoint expectedPoint1 = new MapPoint(141.78806755555556, 119.0503853635612); javafx.geometry.Point2D expectedPoint1 = new javafx.geometry.Point2D(141.78806755555556, 119.0503853635612);
assertEquals(expectedPoint1.getX(), actualPoint1.getX(), 0.0001); assertEquals(expectedPoint1.getX(), actualPoint1.getX(), 0.0001);
assertEquals(expectedPoint1.getY(), actualPoint1.getY(), 0.0001); assertEquals(expectedPoint1.getY(), actualPoint1.getY(), 0.0001);
MapGeo geo2 = new MapGeo(77.456432, -23.456462); GeoPoint geo2 = new GeoPoint(77.456432, -23.456462);
MapPoint actualPoint2 = MercatorProjection.toMapPoint(geo2); javafx.geometry.Point2D actualPoint2 = MercatorProjection.toMapPoint(geo2);
MapPoint expectedPoint2 = new MapPoint(111.31984924444444, 38.03143323746788); javafx.geometry.Point2D expectedPoint2 = new javafx.geometry.Point2D(111.31984924444444, 38.03143323746788);
assertEquals(expectedPoint2.getX(), actualPoint2.getX(), 0.0001); assertEquals(expectedPoint2.getX(), actualPoint2.getX(), 0.0001);
assertEquals(expectedPoint2.getY(), actualPoint2.getY(), 0.0001); assertEquals(expectedPoint2.getY(), actualPoint2.getY(), 0.0001);
} }
@Test @Test
public void toMapGeo() throws Exception { public void toMapGeo() throws Exception {
MapPoint point1 = new MapPoint(123.1234, 25.4565); javafx.geometry.Point2D point1 = new javafx.geometry.Point2D(123.1234, 25.4565);
MapGeo actualGeo1 = MercatorProjection.toMapGeo(point1); GeoPoint actualGeo1 = MercatorProjection.toMapGeo(point1);
MapGeo expectedGeo1 = new MapGeo(80.77043127275441, -6.857718749999995); GeoPoint expectedGeo1 = new GeoPoint(80.77043127275441, -6.857718749999995);
assertEquals(expectedGeo1.getLat(), actualGeo1.getLat(), 0.0001); assertEquals(expectedGeo1.getLat(), actualGeo1.getLat(), 0.0001);
assertEquals(expectedGeo1.getLng(), actualGeo1.getLng(), 0.0001); assertEquals(expectedGeo1.getLng(), actualGeo1.getLng(), 0.0001);
MapPoint point2 = new MapPoint(1.235, 255.4565); javafx.geometry.Point2D point2 = new javafx.geometry.Point2D(1.235, 255.4565);
MapGeo actualGeo2 = MercatorProjection.toMapGeo(point2); GeoPoint actualGeo2 = MercatorProjection.toMapGeo(point2);
MapGeo expectedGeo2 = new MapGeo(-84.98475532898011, -178.26328125); GeoPoint expectedGeo2 = new GeoPoint(-84.98475532898011, -178.26328125);
assertEquals(expectedGeo2.getLat(), actualGeo2.getLat(), 0.0001); assertEquals(expectedGeo2.getLat(), actualGeo2.getLat(), 0.0001);
assertEquals(expectedGeo2.getLng(), actualGeo2.getLng(), 0.0001); assertEquals(expectedGeo2.getLng(), actualGeo2.getLng(), 0.0001);
} }
@@ -61,16 +61,16 @@ public class StreamReceiverTest {
assert pq.size() == 0; assert pq.size() == 0;
} }
@Test // @Test
public void connectReadsAPacket() throws Exception { // public void connectReadsAPacket() throws Exception {
Socket host=mock(Socket.class); // Socket host=mock(Socket.class);
InputStream stream = new ByteArrayInputStream(workingPacket); // InputStream stream = new ByteArrayInputStream(workingPacket);
when(host.getInputStream()).thenReturn(stream); // when(host.getInputStream()).thenReturn(stream);
StreamReceiver streamReceiver = new StreamReceiver(host, pq); // StreamReceiver streamReceiver = new StreamReceiver(host, pq);
//
streamReceiver.connect(); // streamReceiver.connect();
assert pq.size() == 1; // assert pq.size() == 1;
} // }
@Test @Test
public void connectDropsAMismatchedCrc() throws Exception { public void connectDropsAMismatchedCrc() throws Exception {
@@ -1,7 +1,8 @@
package seng302.server.simulator; package seng302.server.simulator;
import org.junit.Test; import org.junit.Test;
import seng302.server.simulator.mark.Position; import seng302.utilities.GeoPoint;
import seng302.utilities.GeoUtility;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -11,10 +12,10 @@ import static org.junit.Assert.*;
*/ */
public class GeoUtilityTest { public class GeoUtilityTest {
private Position p1 = new Position(57.670333, 11.827833); private GeoPoint p1 = new GeoPoint(57.670333, 11.827833);
private Position p2 = new Position(57.671524, 11.844495); private GeoPoint p2 = new GeoPoint(57.671524, 11.844495);
private Position p3 = new Position(57.670822, 11.843392); private GeoPoint p3 = new GeoPoint(57.670822, 11.843392);
private Position p4 = new Position(25.694829, 98.392049); private GeoPoint p4 = new GeoPoint(25.694829, 98.392049);
private double toleranceRate = 0.01; private double toleranceRate = 0.01;
@@ -54,7 +55,7 @@ public class GeoUtilityTest {
@Test @Test
public void getGeoCoordinate() throws Exception { public void getGeoCoordinate() throws Exception {
Position expected, actual; GeoPoint expected, actual;
actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0); actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0);
expected = p2; expected = p2;