Merge branch 'develop' into 1124_Mark_Sequence_From_RaceXML

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

Some files were not shown because too many files have changed in this diff Show More