Merge branch 'develop' into story61_player_perspective

# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/client/ClientPacketParser.java
#	src/main/java/seng302/client/ClientState.java
#	src/main/java/seng302/client/ClientStateQueryingRunnable.java
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/controllers/LobbyController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/fxObjects/BoatAnnotations.java
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/model/stream/StreamReceiver.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/fxObjects/BoatObject.java
#	src/test/java/seng302/model/stream/StreamReceiverTest.java
This commit is contained in:
Calum
2017-07-30 18:07:28 +12:00
30 changed files with 1301 additions and 188 deletions
+2
View File
@@ -27,9 +27,11 @@ public class App extends Application {
primaryStage.setOnCloseRequest(e -> { primaryStage.setOnCloseRequest(e -> {
// ClientPacketParser.appClose(); // ClientPacketParser.appClose();
StreamReceiver.noMoreBytes(); StreamReceiver.noMoreBytes();
ClientPacketParser.appClose();
System.exit(0); System.exit(0);
}); });
ClientState.primaryStage = primaryStage;
} }
public static void main(String[] args) { public static void main(String[] args) {
@@ -0,0 +1,638 @@
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;
}
}
}
@@ -3,6 +3,8 @@ package seng302.client;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import seng302.model.Yacht; import seng302.model.Yacht;
import javafx.stage.Stage;
import seng302.models.Yacht;
/** /**
* Used by the client to store static variables to be used in game. * Used by the client to store static variables to be used in game.
@@ -10,6 +10,7 @@
//import javafx.scene.input.KeyEvent; //import javafx.scene.input.KeyEvent;
//import javafx.scene.layout.AnchorPane; //import javafx.scene.layout.AnchorPane;
//import seng302.client.ClientPacketParser; //import seng302.client.ClientPacketParser;
//import seng302.client.ClientState;
//import seng302.client.ClientToServerThread; //import seng302.client.ClientToServerThread;
//import seng302.server.messages.BoatActionMessage; //import seng302.server.messages.BoatActionMessage;
//import seng302.server.messages.BoatActionType; //import seng302.server.messages.BoatActionType;
@@ -22,7 +23,7 @@
// private long lastSendingTime; // private long lastSendingTime;
// private int KEY_STROKE_SENDING_FREQUENCY = 50; // private int KEY_STROKE_SENDING_FREQUENCY = 50;
// //
// private Object setContentPane(String jfxUrl) { // public Object setContentPane(String jfxUrl) {
// try { // try {
// contentPane.getChildren().removeAll(); // contentPane.getChildren().removeAll();
// contentPane.getChildren().clear(); // contentPane.getChildren().clear();
@@ -41,19 +42,25 @@
// //
// @Override // @Override
// public void initialize(URL location, ResourceBundle resources) { // 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()); // contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// StartScreenController startScreenController = (StartScreenController) setContentPane("/views/StartScreenView.fxml"); // StartScreenController startScreenController = (StartScreenController) setContentPane("/views/StartScreenView.fxml");
// startScreenController.setController(this); // startScreenController.setController(this);
// ClientPacketParser.boatLocations.clear(); // ClientPacketParser.boatLocations.clear();
//
// lastSendingTime = System.currentTimeMillis();
// } // }
// //
//
// /** Handle the key-pressed event from the text field. */ // /** Handle the key-pressed event from the text field. */
// public void keyPressed(KeyEvent e) { // public void keyPressed(KeyEvent e) {
// BoatActionMessage boatActionMessage; // BoatActionMessage boatActionMessage;
// long currentTime = System.currentTimeMillis(); // long currentTime = System.currentTimeMillis();
// if (currentTime - lastSendingTime > KEY_STROKE_SENDING_FREQUENCY) { // if (currentTime - lastSendingTime > KEY_STROKE_SENDING_FREQUENCY && ClientState.isRaceStarted()) {
// lastSendingTime = currentTime; // lastSendingTime = currentTime;
// switch (e.getCode()) { // switch (e.getCode()) {
// case SPACE: // align with vmg // case SPACE: // align with vmg
@@ -0,0 +1,235 @@
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;
}
}
@@ -145,7 +145,7 @@
// controller.loadState(importantAnnotations); // controller.loadState(importantAnnotations);
// //
// } catch (IOException e) { // } catch (IOException e) {
// e.printStackTrace(); // System.out.println("[RaceViewController] IO exception");
// } // }
// } // }
// //
@@ -287,6 +287,7 @@
// updateWindDirection(); // updateWindDirection();
//// updateOrder(); //// updateOrder();
// updateBoatSelectionComboBox(); // updateBoatSelectionComboBox();
// updateOrder();
// }) // })
// ); // );
// //
@@ -383,9 +384,12 @@
// } // }
// //
// if (ClientPacketParser.isRaceStarted()) { // if (ClientPacketParser.isRaceStarted()) {
// /*
// for (Yacht boat : ClientPacketParser.getBoatsPos().values()) { // for (Yacht boat : ClientPacketParser.getBoatsPos().values()) {
// if (participantIDs.contains(boat.getSourceId())) { // check if the boat is racing // System.out.println("Hi tjere" + boat.getBoatName());
// if (boat.getBoatStatus() == 3) { // 3 is finish status // 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() + ". " + // Text textToAdd = new Text(boat.getPosition() + ". " +
// boat.getShortName() + " (Finished)"); // boat.getShortName() + " (Finished)");
// textToAdd.setFill(Paint.valueOf("#d3d3d3")); // textToAdd.setFill(Paint.valueOf("#d3d3d3"));
@@ -397,9 +401,17 @@
// textToAdd.setFill(Paint.valueOf("#d3d3d3")); // textToAdd.setFill(Paint.valueOf("#d3d3d3"));
// textToAdd.setStyle(""); // textToAdd.setStyle("");
// positionVbox.getChildren().add(textToAdd); // 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 { // } else {
// for (Yacht boat : ClientPacketParser.getBoats().values()) { // for (Yacht boat : ClientPacketParser.getBoats().values()) {
// if (participantIDs.contains(boat.getSourceId())) { // check if the boat is racing // if (participantIDs.contains(boat.getSourceId())) { // check if the boat is racing
@@ -51,9 +51,9 @@
// //
// return fxmlLoader.getController(); // return fxmlLoader.getController();
// } catch (javafx.fxml.LoadException e) { // } catch (javafx.fxml.LoadException e) {
// e.printStackTrace(); // System.out.println("[Controller] FXML load exception");
// } catch (IOException e) { // } catch (IOException e) {
// e.printStackTrace(); // System.out.println("[Controller] IO exception");
// } // }
// return null; // return null;
// } // }
@@ -69,6 +69,7 @@
// @FXML // @FXML
// public void hostButtonPressed() { // public void hostButtonPressed() {
// try { // try {
// String ipAddress = InetAddress.getLocalHost().getHostAddress();
// // get the lobby controller so that we can pass the game server thread to it // // get the lobby controller so that we can pass the game server thread to it
// new GameState(getLocalHostIp()); // new GameState(getLocalHostIp());
// MainServerThread mainServerThread = new MainServerThread(); // MainServerThread mainServerThread = new MainServerThread();
@@ -80,12 +81,12 @@
// controller.setClientToServerThread(clientToServerThread); // controller.setClientToServerThread(clientToServerThread);
// LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml"); // LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml");
// lobbyController.setMainServerThread(mainServerThread); // lobbyController.setMainServerThread(mainServerThread);
// lobbyController.setController(controller);
// } catch (Exception e) { // } catch (Exception e) {
// Alert alert = new Alert(AlertType.ERROR); // Alert alert = new Alert(AlertType.ERROR);
// alert.setHeaderText("Cannot host"); // alert.setHeaderText("Cannot host");
// alert.setContentText("Oops, failed to host, try to restart."); // alert.setContentText("Oops, failed to host, try to restart.");
// alert.showAndWait(); // alert.showAndWait();
// e.printStackTrace();
// } // }
// //
// //
@@ -108,8 +109,10 @@
// ClientState.setHost(false); // ClientState.setHost(false);
// ClientState.setConnectedToHost(true); // ClientState.setConnectedToHost(true);
// //
// ClientState.setHostIp(ipAddress);
// controller.setClientToServerThread(clientToServerThread); // controller.setClientToServerThread(clientToServerThread);
// setContentPane("/views/LobbyView.fxml"); // LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml");
// lobbyController.setController(controller);
// } catch (Exception e) { // } catch (Exception e) {
// Alert alert = new Alert(AlertType.ERROR); // Alert alert = new Alert(AlertType.ERROR);
// alert.setHeaderText("Cannot reach the host"); // alert.setHeaderText("Cannot reach the host");
@@ -150,7 +153,7 @@
// } // }
// } // }
// } catch (Exception e) { // } catch (Exception e) {
// e.printStackTrace(); // System.out.println("[StartScreenController] Exception");
// } // }
// if (ipAddress == null) { // if (ipAddress == null) {
// System.out.println("[HOST] Cannot obtain local host ip address."); // System.out.println("[HOST] Cannot obtain local host ip address.");
+51 -11
View File
@@ -13,7 +13,9 @@ import seng302.server.messages.BoatActionType;
* A Static class to hold information about the current state of the game (model) * A Static class to hold information about the current state of the game (model)
* Created by wmu16 on 10/07/17. * Created by wmu16 on 10/07/17.
*/ */
public class GameState { public class GameState implements Runnable {
private static Integer STATE_UPDATES_PER_SECOND = 60;
private static Long previousUpdateTime; private static Long previousUpdateTime;
public static Double windDirection; public static Double windDirection;
@@ -40,8 +42,9 @@ public class GameState {
*/ */
public GameState(String hostIpAddress) { public GameState(String hostIpAddress) {
windDirection = 170d; windDirection = 180d;
windSpeed = 10000d; windSpeed = 10000d;
this.hostIpAddress = hostIpAddress;
yachts = new HashMap<>(); yachts = new HashMap<>();
players = new ArrayList<>(); players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress; GameState.hostIpAddress = hostIpAddress;
@@ -52,6 +55,9 @@ public class GameState {
//set this when game stage changes to prerace //set this when game stage changes to prerace
previousUpdateTime = System.currentTimeMillis(); previousUpdateTime = System.currentTimeMillis();
yachts = new HashMap<>(); yachts = new HashMap<>();
new Thread(this).start();
} }
public static String getHostIpAddress() { public static String getHostIpAddress() {
@@ -96,9 +102,17 @@ public class GameState {
} }
public static void setCurrentStage(GameStages currentStage) { public static void setCurrentStage(GameStages currentStage) {
if (currentStage == GameStages.RACING){
startTime = System.currentTimeMillis();
}
GameState.currentStage = currentStage; GameState.currentStage = currentStage;
} }
public static long getStartTime(){
return startTime;
}
public static Double getWindDirection() { public static Double getWindDirection() {
return windDirection; return windDirection;
} }
@@ -122,7 +136,6 @@ public class GameState {
case VMG: case VMG:
playerYacht.turnToVMG(); playerYacht.turnToVMG();
// System.out.println("Snapping to VMG"); // System.out.println("Snapping to VMG");
// TODO: 22/07/17 wmu16 - Add in the vmg calculation code here
break; break;
case SAILS_IN: case SAILS_IN:
playerYacht.toggleSailIn(); playerYacht.toggleSailIn();
@@ -146,17 +159,10 @@ public class GameState {
break; break;
} }
System.out.println("-----------------------"); // printBoatStatus(playerYacht);
System.out.println("Sails are in: " + playerYacht.getSailIn());
System.out.println("Heading: " + playerYacht.getHeading());
System.out.println("Velocity: " + playerYacht.getVelocityMMS() / 1000);
System.out.println("Lat: " + playerYacht.getLocation().getLat());
System.out.println("Lng: " + playerYacht.getLocation().getLng());
System.out.println("-----------------------\n");
} }
public static void update() { public static void update() {
Long timeInterval = System.currentTimeMillis() - previousUpdateTime; Long timeInterval = System.currentTimeMillis() - previousUpdateTime;
previousUpdateTime = System.currentTimeMillis(); previousUpdateTime = System.currentTimeMillis();
for (Yacht yacht : yachts.values()) { for (Yacht yacht : yachts.values()) {
@@ -173,4 +179,38 @@ public class GameState {
// TODO: 22/07/17 wmu16 - This may not be robust enough and may have to be improved on. // TODO: 22/07/17 wmu16 - This may not be robust enough and may have to be improved on.
return yachts.size() + 1; return yachts.size() + 1;
} }
/**
* A thread to have the game state update itself at certain intervals
*/
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000 / STATE_UPDATES_PER_SECOND);
} catch (InterruptedException e) {
System.out.println("[GameState] interrupted exception");
}
if (currentStage == GameStages.PRE_RACE) {
update();
}
//RACING
if (currentStage == GameStages.RACING) {
update();
}
}
}
private static void printBoatStatus(Yacht playerYacht) {
System.out.println("-----------------------");
System.out.println("Sails are in: " + playerYacht.getSailIn());
System.out.println("Heading: " + playerYacht.getHeading());
System.out.println("Velocity: " + playerYacht.getVelocityMMS() / 1000);
System.out.println("Lat: " + playerYacht.getLocation().getLat());
System.out.println("Lng: " + playerYacht.getLocation().getLng());
System.out.println("-----------------------\n");
}
} }
@@ -2,35 +2,36 @@ package seng302.gameServer;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Observable; 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.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
import seng302.model.Player; import java.util.logging.Logger;
import seng302.model.stream.PacketBufferDelegate;
import seng302.model.stream.packets.StreamPacket;
/** /**
* A class describing the overall server, which creates and collects server threads for each client * A class describing the overall server, which creates and collects server threads for each client
* Created by wmu16 on 13/07/17. * Created by wmu16 on 13/07/17.
*/ */
public class MainServerThread extends Observable implements Runnable, PacketBufferDelegate, ClientConnectionDelegate{ public class MainServerThread extends Observable implements Runnable, ClientConnectionDelegate{
private static final int PORT = 4942; private static final int PORT = 4942;
private static final Integer MAX_NUM_PLAYERS = 3; private static final Integer CLIENT_UPDATES_PER_SECOND = 10;
private static final Integer UPDATES_PER_SECOND = 2;
private static final int LOG_LEVEL = 1; private static final int LOG_LEVEL = 1;
private boolean terminated;
private Thread thread; private Thread thread;
private ServerSocket serverSocket = null; private ServerSocket serverSocket = null;
private Socket socket;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>(); private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
private PriorityBlockingQueue<StreamPacket> packetBuffer;
public MainServerThread() { public MainServerThread() {
try { try {
serverSocket = new ServerSocket(PORT); serverSocket = new ServerSocket(PORT);
@@ -38,8 +39,7 @@ public class MainServerThread extends Observable implements Runnable, PacketBuff
serverLog("IO error in server thread handler upon trying to make new server socket", 0); serverLog("IO error in server thread handler upon trying to make new server socket", 0);
} }
packetBuffer = new PriorityBlockingQueue<>(); terminated = false;
thread = new Thread(this); thread = new Thread(this);
thread.start(); thread.start();
} }
@@ -55,22 +55,20 @@ public class MainServerThread extends Observable implements Runnable, PacketBuff
heartbeatThread.start(); heartbeatThread.start();
serverListenThread.start(); serverListenThread.start();
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app. //You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
while (!thread.isInterrupted()) { while (!terminated) {
try { try {
Thread.sleep(1000 / UPDATES_PER_SECOND); Thread.sleep(1000 / CLIENT_UPDATES_PER_SECOND);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); serverLog("Interrupted exception in Main Server Thread thread sleep", 1);
} }
if (GameState.getCurrentStage() == GameStages.PRE_RACE) { if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
GameState.update(); updateClients();
} }
//RACING //RACING
if (GameState.getCurrentStage() == GameStages.RACING) { if (GameState.getCurrentStage() == GameStages.RACING) {
GameState.update();
updateClients(); updateClients();
} }
@@ -78,15 +76,6 @@ public class MainServerThread extends Observable implements Runnable, PacketBuff
else if (GameState.getCurrentStage() == GameStages.FINISHED) { else if (GameState.getCurrentStage() == GameStages.FINISHED) {
} }
while (!packetBuffer.isEmpty()){
try {
StreamPacket packet = packetBuffer.take();
// ClientPacketParser.parsePacket(packet);
} catch (InterruptedException e) {
continue;
}
}
} }
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients // TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
@@ -111,11 +100,6 @@ public class MainServerThread extends Observable implements Runnable, PacketBuff
} }
} }
@Override
public boolean addToBuffer(StreamPacket streamPacket) {
return packetBuffer.add(streamPacket);
}
/** /**
* A client has tried to connect to the server * A client has tried to connect to the server
* @param serverToClientThread The player that connected * @param serverToClientThread The player that connected
@@ -153,20 +137,20 @@ public class MainServerThread extends Observable implements Runnable, PacketBuff
} }
public void startGame() { public void startGame() {
for (ServerToClientThread serverToClientThread : serverToClientThreads) { Timer t = new Timer();
serverToClientThread.sendRaceStatusMessage();
} t.schedule(new TimerTask() {
@Override
public void run() {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.sendRaceStatusMessage();
}
}
}, 0, 500);
} }
public void shutDown() { public void terminate() {
for (ServerToClientThread serverToClientThread : serverToClientThreads) { terminated = true;
try {
serverToClientThread.getSocket().close();
} catch (IOException ioe) {
serverLog("Failed to close socket " + serverToClientThread.getSocket().toString(), 0);
}
}
serverToClientThreads = null;
thread = null;
} }
} }
@@ -23,9 +23,11 @@ public class ServerListenThread extends Thread{
private void acceptConnection() { private void acceptConnection() {
try { try {
Socket thisClient = serverSocket.accept(); Socket thisClient = serverSocket.accept();
if (thisClient != null){ if (thisClient != null && GameState.getCurrentStage().equals(GameStages.LOBBYING)) {
ServerToClientThread thisConnection = new ServerToClientThread(thisClient); ServerToClientThread thisConnection = new ServerToClientThread(thisClient);
delegate.clientConnected(thisConnection); delegate.clientConnected(thisConnection);
} else {
thisClient.close();
} }
} catch (IOException e) { } catch (IOException e) {
e.getMessage(); e.getMessage();
@@ -1,9 +1,12 @@
package seng302.gameServer; package seng302.gameServer;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
@@ -12,6 +15,9 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Observable; import java.util.Observable;
import java.util.Observer; 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.CRC32;
import java.util.zip.Checksum; import java.util.zip.Checksum;
import seng302.model.Player; import seng302.model.Player;
@@ -63,17 +69,41 @@ public class ServerToClientThread implements Runnable, Observer {
public ServerToClientThread(Socket socket) { public ServerToClientThread(Socket socket) {
this.socket = socket; this.socket = socket;
BufferedReader fn;
String fName = "";
BufferedReader ln;
String lName = "";
try { try {
is = socket.getInputStream(); is = socket.getInputStream();
os = socket.getOutputStream(); os = socket.getOutputStream();
fn = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_First_Names.csv"
)
)
);
List<String> all = fn.lines().collect(Collectors.toList());
fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
ln = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_Last_Names.csv"
)
)
);
all = ln.lines().collect(Collectors.toList());
lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
} catch (IOException e) { } catch (IOException e) {
System.out.println("IO error in server thread upon grabbing streams"); serverLog("IO error in server thread upon grabbing streams", 1);
} }
//Attempt threeway handshake with connection //Attempt threeway handshake with connection
sourceId = GameState.getUniquePlayerID(); sourceId = GameState.getUniquePlayerID();
if (threeWayHandshake(sourceId)) { if (threeWayHandshake(sourceId)) {
serverLog("Successful handshake. Client allocated id: " + sourceId, 1); serverLog("Successful handshake. Client allocated id: " + sourceId, 0);
Yacht yacht = new Yacht("Yacht", sourceId, sourceId.toString(), "Kapa", "Kappa", "NZ"); Yacht yacht = new Yacht(
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
);
// Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0); // Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0);
GameState.addYacht(sourceId, yacht); GameState.addYacht(sourceId, yacht);
GameState.addPlayer(new Player(socket, yacht)); GameState.addPlayer(new Player(socket, yacht));
@@ -108,11 +138,6 @@ public class ServerToClientThread implements Runnable, Observer {
while (socket.isConnected()) { while (socket.isConnected()) {
try { try {
// if (initialisedRace) {
// sendSetupMessages();
// initialisedRace = false;
// }
//Perform a write if it is time to as delegated by the MainServerThread //Perform a write if it is time to as delegated by the MainServerThread
if (updateClient) { if (updateClient) {
// TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream // TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream
@@ -159,12 +184,10 @@ public class ServerToClientThread implements Runnable, Observer {
} catch (Exception e) { } catch (Exception e) {
// TODO: 24/07/17 zyt10 - fix a logic here when a client disconnected // TODO: 24/07/17 zyt10 - fix a logic here when a client disconnected
// serverLog("ERROR OCCURRED, CLOSING SERVER CONNECTION: " + socket.getRemoteSocketAddress().toString(), 1); // serverLog("ERROR OCCURRED, CLOSING SERVER CONNECTION: " + socket.getRemoteSocketAddress().toString(), 1);
// e.printStackTrace();
closeSocket(); closeSocket();
return; return;
} }
} }
} }
private void sendSetupMessages() { private void sendSetupMessages() {
@@ -179,18 +202,18 @@ public class ServerToClientThread implements Runnable, Observer {
xml.setRegatta(new Regatta("RaceVision Test Game", 57.6679590, 11.8503233)); xml.setRegatta(new Regatta("RaceVision Test Game", 57.6679590, 11.8503233));
xml.setRace(race); xml.setRace(race);
XMLMessage xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA, XMLMessage xmlMessage;
xml.getRegattaAsXml().length()); xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA,
xml.getRegattaAsXml().length());
sendMessage(xmlMessage); sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT, xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT,
xml.getBoatsAsXml().length()); xml.getBoatsAsXml().length());
sendMessage(xmlMessage); sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE, xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE,
xml.getRaceAsXml().length()); xml.getRaceAsXml().length());
sendMessage(xmlMessage); sendMessage(xmlMessage);
// System.out.println("Sent xml messages for " + thread.getName());
} }
public void updateClient() { public void updateClient() {
@@ -217,7 +240,7 @@ public class ServerToClientThread implements Runnable, Observer {
os.write(id); //Send out new ID looking for echo os.write(id); //Send out new ID looking for echo
confirmationID = is.read(); confirmationID = is.read();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); serverLog("Three way handshake failed", 1);
} }
if (id.equals(confirmationID)) { //ID is echoed back. Connection is a client if (id.equals(confirmationID)) { //ID is echoed back. Connection is a client
@@ -247,7 +270,7 @@ public class ServerToClientThread implements Runnable, Observer {
currentByte = is.read(); currentByte = is.read();
crcBuffer.write(currentByte); crcBuffer.write(currentByte);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); serverLog("Socket read failed", 1);
} }
if (currentByte == -1) { if (currentByte == -1) {
throw new Exception(); throw new Exception();
@@ -273,10 +296,10 @@ public class ServerToClientThread implements Runnable, Observer {
try { try {
os.write(message.getBuffer()); os.write(message.getBuffer());
} catch (SocketException e) { } catch (SocketException e) {
//serverLog("Player " + sourceId + " side socket disconnected", 0); //serverLog("Player " + sourceId + " side socket disconnected", 1);
return; return;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); serverLog("Message send failed", 1);
} }
} }
@@ -309,8 +332,7 @@ public class ServerToClientThread implements Runnable, Observer {
public void sendRaceStatusMessage() { public void sendRaceStatusMessage() {
// variables taken from GameServerThread // variables taken from GameServerThread
int TIME_TILL_RACE_START = 20 * 1000;
long startTime = System.currentTimeMillis() + TIME_TILL_RACE_START;
List<BoatSubMessage> boatSubMessages = new ArrayList<>(); List<BoatSubMessage> boatSubMessages = new ArrayList<>();
BoatStatus boatStatus; BoatStatus boatStatus;
@@ -338,7 +360,7 @@ public class ServerToClientThread implements Runnable, Observer {
raceStatus = RaceStatus.WARNING; raceStatus = RaceStatus.WARNING;
} }
sendMessage(new RaceStatusMessage(1, raceStatus, startTime, GameState.getWindDirection(), sendMessage(new RaceStatusMessage(1, raceStatus, GameState.getStartTime(), GameState.getWindDirection(),
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(), GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
RaceType.MATCH_RACE, 1, boatSubMessages)); RaceType.MATCH_RACE, 1, boatSubMessages));
} }
+2 -3
View File
@@ -4,8 +4,6 @@ import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
/** /**
@@ -70,7 +68,7 @@ public final class PolarTable {
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); System.out.println("[PolarTable] IO exception");
} }
@@ -182,4 +180,5 @@ public final class PolarTable {
return closestAngle; return closestAngle;
} }
} }
+81 -7
View File
@@ -12,6 +12,10 @@ import seng302.model.mark.Mark;
import static seng302.utilities.GeoUtility.getGeoCoordinate; import static seng302.utilities.GeoUtility.getGeoCoordinate;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.HashMap;
import javafx.scene.paint.Color;
import seng302.client.ClientPacketParser;
import seng302.controllers.RaceViewController;
import seng302.gameServer.GameState; import seng302.gameServer.GameState;
import seng302.utilities.GeoPoint; import seng302.utilities.GeoPoint;
@@ -128,6 +132,33 @@ public class Yacht {
* @param timeInterval since last update in milliseconds * @param timeInterval since last update in milliseconds
*/ */
public void update(Long timeInterval) { public void update(Long timeInterval) {
Double secondsElapsed = timeInterval / 1000000.0;
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;
if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) {
if (velocity < maxBoatSpeed) {
velocity += maxBoatSpeed / 15; // Acceleration
}
if (velocity > maxBoatSpeed) {
velocity = maxBoatSpeed; // Prevent the boats from exceeding top speed
}
} else { // Deceleration
if (velocity > 0d) {
if (maxBoatSpeed != 0d) {
velocity -= maxBoatSpeed / 600;
} else {
velocity -= velocity / 100;
}
if (velocity < 0) {
velocity = 0d;
}
}
if (sailIn) { if (sailIn) {
Double secondsElapsed = timeInterval / 1000000.0; Double secondsElapsed = timeInterval / 1000000.0;
Double windSpeedKnots = GameState.getWindSpeedKnots(); Double windSpeedKnots = GameState.getWindSpeedKnots();
@@ -141,6 +172,17 @@ public class Yacht {
} }
} }
}
Double metersCovered = velocity * secondsElapsed;
location = getGeoCoordinate(location, heading, metersCovered);
}
public Double getHeading() {
return heading;
}
public void adjustHeading(Double amount) { public void adjustHeading(Double amount) {
Double newVal = heading + amount; Double newVal = heading + amount;
lastHeading = heading; lastHeading = heading;
@@ -149,8 +191,7 @@ public class Yacht {
} }
public void tackGybe(Double windDirection) { public void tackGybe(Double windDirection) {
Double normalizedHeading = heading - GameState.windDirection; Double normalizedHeading = normalizeHeading();
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360);
adjustHeading(-2 * normalizedHeading); adjustHeading(-2 * normalizedHeading);
} }
@@ -159,8 +200,7 @@ public class Yacht {
} }
public void turnUpwind() { public void turnUpwind() {
Double normalizedHeading = heading - GameState.windDirection; Double normalizedHeading = normalizeHeading();
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360);
if (normalizedHeading == 0) { if (normalizedHeading == 0) {
if (lastHeading < 180) { if (lastHeading < 180) {
adjustHeading(-TURN_STEP); adjustHeading(-TURN_STEP);
@@ -181,8 +221,7 @@ public class Yacht {
} }
public void turnDownwind() { public void turnDownwind() {
Double normalizedHeading = heading - GameState.windDirection; Double normalizedHeading = normalizeHeading();
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360);
if (normalizedHeading == 0) { if (normalizedHeading == 0) {
if (lastHeading < 180) { if (lastHeading < 180) {
adjustHeading(TURN_STEP); adjustHeading(TURN_STEP);
@@ -203,7 +242,42 @@ public class Yacht {
} }
public void turnToVMG() { public void turnToVMG() {
// TODO: 25/07/17 wmu16 - Fix this so it grabs the optimal value from the optimal Polar Double normalizedHeading = normalizeHeading();
Double optimalHeading;
HashMap<Double, Double> optimalPolarMap;
if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind
optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots());
optimalHeading = optimalPolarMap.keySet().iterator().next();
} else {
optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots());
optimalHeading = optimalPolarMap.keySet().iterator().next();
}
// Take optimal heading and turn into correct
optimalHeading =
optimalHeading + (double) Math.floorMod(GameState.getWindDirection().longValue(), 360L);
turnTowardsHeading(optimalHeading);
}
private void turnTowardsHeading(Double newHeading) {
System.out.println(newHeading);
if (heading < 90 && newHeading > 270) {
adjustHeading(-TURN_STEP);
} else {
if (heading < newHeading) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
}
}
private Double normalizeHeading() {
Double normalizedHeading = heading - GameState.windDirection;
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
return normalizedHeading;
} }
public String getBoatType() { public String getBoatType() {
@@ -37,7 +37,7 @@ public class CanvasMap {
return new Image(connection.getInputStream()); return new Image(connection.getInputStream());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); System.out.println("[CanvasMap] Exception");
return null; return null;
} }
} }
@@ -62,7 +62,7 @@ public class Simulator extends Observable implements Runnable {
try { try {
Thread.sleep(lapse); Thread.sleep(lapse);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); System.out.println("[Simulator] interrupted exception ");
} }
} }
} }
@@ -31,7 +31,7 @@ public abstract class FileParser {
doc.getDocumentElement().normalize(); doc.getDocumentElement().normalize();
return doc; return doc;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); System.out.println("[FileParser] Exception");
return null; return null;
} }
} }
@@ -45,7 +45,7 @@ public abstract class FileParser {
doc.getDocumentElement().normalize(); doc.getDocumentElement().normalize();
return doc; return doc;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); System.out.println("[FileParser] Exception");
} }
return null; return null;
} }
@@ -18,7 +18,8 @@ import seng302.server.messages.BoatActionMessage;
import seng302.server.messages.Message; import seng302.server.messages.Message;
/** /**
* Created by kre39 on 13/07/17. * A class describing a single connection to a Server for the purposes of sending and receiving on
* its own thread.
*/ */
public class ClientToServerThread implements Runnable { public class ClientToServerThread implements Runnable {
private static final int LOG_LEVEL = 1; private static final int LOG_LEVEL = 1;
@@ -34,9 +35,21 @@ public class ClientToServerThread implements Runnable {
private int clientId; private int clientId;
private Boolean updateClient = true; private Boolean updateClient = true;
private ByteArrayOutputStream crcBuffer; private ByteArrayOutputStream crcBuffer;
public ClientToServerThread(String ipAddress, Integer portNumber) throws IOException{ /**
* Constructor for ClientToServerThread which takes in ipAddress and portNumber and attempts to
* connect to the specified ipAddress and port.
*
* Upon successful socket connection, threeWayHandshake will be preformed and the instance will
* be put on a thread and run immediately.
*
* @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
* combination
*/
public ClientToServerThread(String ipAddress, Integer portNumber) throws IOException {
socket = new Socket(ipAddress, portNumber); socket = new Socket(ipAddress, portNumber);
is = socket.getInputStream(); is = socket.getInputStream();
os = socket.getOutputStream(); os = socket.getOutputStream();
@@ -55,33 +68,35 @@ public class ClientToServerThread implements Runnable {
thread.start(); thread.start();
} }
static void clientLog(String message, int logLevel){ /**
if(logLevel <= LOG_LEVEL){ * Prints out log messages and the time happened.
System.out.println("[CLIENT " + LocalDateTime.now().toLocalTime().toString() + "] " + message); * Only perform task if log level is below LOG_LEVEL variable.
*
* @param message a string of message to be printed out
* @param logLevel an int for log level
*/
static void clientLog(String message, int logLevel) {
if (logLevel <= LOG_LEVEL) {
System.out.println(
"[CLIENT " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
} }
} }
/**
* Perform the thread loop. It exits the loop if ClientState connected to host
* variable is false.
*/
public void run() { public void run() {
int sync1; int sync1;
int sync2; int sync2;
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop // TODO: 14/07/17 wmu16 - Work out how to fix this while loop
while(true) { /**REMOVED SOMETHING HERE ClientState.isConnectedToHost() */ while(true) { /**REMOVED SOMETHING HERE ClientState.isConnectedToHost() */
try { try {
//Perform a write if it is time to as delegated by the MainServerThread
if (updateClient) {
// TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream
// try {
// GameState.outputState(os);
// } catch (IOException e) {
// System.out.println("IO error in server thread upon writing to output stream");
// }
updateClient = false;
}
crcBuffer = new ByteArrayOutputStream(); crcBuffer = new ByteArrayOutputStream();
sync1 = readByte(); sync1 = readByte();
sync2 = readByte(); sync2 = readByte();
//checking if it is the start of the packet //checking if it is the start of the packet
if(sync1 == 0x47 && sync2 == 0x83) { if (sync1 == 0x47 && sync2 == 0x83) {
int type = readByte(); int type = readByte();
//No. of milliseconds since Jan 1st 1970 //No. of milliseconds since Jan 1st 1970
long timeStamp = Message.bytesToLong(getBytes(6)); long timeStamp = Message.bytesToLong(getBytes(6));
@@ -102,7 +117,16 @@ public class ClientToServerThread implements Runnable {
} }
} catch (Exception e) { } catch (Exception e) {
closeSocket(); closeSocket();
e.printStackTrace(); 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);
return; return;
} }
} }
@@ -112,7 +136,8 @@ public class ClientToServerThread implements Runnable {
/** /**
* Listens for an allocated sourceID and returns it to the server if recieved * Listens for an allocated sourceID and returns it to the server
*
* @return the sourceID allocated to us by the server * @return the sourceID allocated to us by the server
*/ */
private Integer threeWayHandshake() { private Integer threeWayHandshake() {
@@ -121,20 +146,22 @@ public class ClientToServerThread implements Runnable {
try { try {
ourSourceID = is.read(); ourSourceID = is.read();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); clientLog("Three way handshake failed", 1);
} }
if (ourSourceID != null) { if (ourSourceID != null) {
try { try {
os.write(ourSourceID); os.write(ourSourceID);
return ourSourceID; return ourSourceID;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); clientLog("Three way handshake failed", 1);
return null; return null;
} }
} }
} }
} }
/** /**
* Send the post-start race course information * Send the post-start race course information
*/ */
@@ -142,8 +169,7 @@ public class ClientToServerThread implements Runnable {
try { try {
os.write(boatActionMessage.getBuffer()); os.write(boatActionMessage.getBuffer());
} catch (IOException e) { } catch (IOException e) {
clientLog("COULD NOT WRITE TO SERVER", 0); clientLog("Could not write to server", 1);
e.printStackTrace();
} }
} }
@@ -152,7 +178,7 @@ public class ClientToServerThread implements Runnable {
try { try {
socket.close(); socket.close();
} catch (IOException e) { } catch (IOException e) {
clientLog("Failed to close the socket", 0); clientLog("Failed to close the socket", 1);
} }
} }
@@ -170,24 +196,24 @@ public class ClientToServerThread implements Runnable {
currentByte = is.read(); currentByte = is.read();
crcBuffer.write(currentByte); crcBuffer.write(currentByte);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); clientLog("Read byte failed", 1);
} }
if (currentByte == -1){ if (currentByte == -1) {
throw new Exception(); throw new Exception();
} }
return currentByte; return currentByte;
} }
private byte[] getBytes(int n) throws Exception{ private byte[] getBytes(int n) throws Exception {
byte[] bytes = new byte[n]; byte[] bytes = new byte[n];
for (int i = 0; i < n; i++){ for (int i = 0; i < n; i++) {
bytes[i] = (byte) readByte(); bytes[i] = (byte) readByte();
} }
return bytes; return bytes;
} }
private void skipBytes(long n) throws Exception{ private void skipBytes(long n) throws Exception {
for (int i=0; i < n; i++){ for (int i = 0; i < n; i++) {
readByte(); readByte();
} }
} }
@@ -45,11 +45,11 @@ public class GameView extends Pane {
private ObservableList<Node> gameObjects; private ObservableList<Node> gameObjects;
private ImageView mapImage; private ImageView mapImage;
private int BUFFER_SIZE = 50; private final int BUFFER_SIZE = 50;
private int panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias. private final int PANEL_WIDTH = 1260; // it should be 1280 but, minors 40 to cancel the bias.
private int panelHeight = 960; private final int PANEL_HEIGHT = 960;
private double canvasWidth = 720; private final int CANVAS_WIDTH = 1100;
private double canvasHeight = 720; private final int CANVAS_HEIGHT = 920;
private boolean horizontalInversion = false; private boolean horizontalInversion = false;
private double distanceScaleFactor; private double distanceScaleFactor;
@@ -154,8 +154,10 @@ public class GameView extends Pane {
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren().addAll( contentPane.getChildren().addAll(
(Pane) FXMLLoader.load(getClass().getResource("/views/FinishScreenView.fxml"))); (Pane) FXMLLoader.load(getClass().getResource("/views/FinishScreenView.fxml")));
} catch (javafx.fxml.LoadException e) {
System.out.println("[Controller] FXML load exception");
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); System.out.println("[Controller] IO exception");
} }
} }
@@ -78,9 +78,9 @@ public class FinishScreenViewController implements Initializable {
contentPane.getChildren() contentPane.getChildren()
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
} catch (javafx.fxml.LoadException e) { } catch (javafx.fxml.LoadException e) {
e.printStackTrace(); System.out.println("[Controller] FXML load exception");
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); System.out.println("[Controller] IO exception");
} }
} }
@@ -135,7 +135,11 @@ public class ImportantAnnotationController implements Initializable {
boatEstTimeToNextMarkSelect.isSelected())); boatEstTimeToNextMarkSelect.isSelected()));
boatElapsedTimeSelect.setOnAction( boatElapsedTimeSelect.setOnAction(
event -> setAnnotation(Annotation.LEGTIME, boatElapsedTimeSelect.isSelected())); event -> setAnnotation(Annotation.LEGTIME, boatElapsedTimeSelect.isSelected()));
// TODO: 26/07/17 cir27 - Create a more robust fix for this when the annotation for the game are decided upon.
boatEstTimeToNextMarkSelect.setVisible(false);
boatEstTimeToNextMarkSelect.setDisable(true);
boatElapsedTimeSelect.setVisible(false);
boatElapsedTimeSelect.setDisable(true);
closeButton.setOnAction(event -> stage.close()); closeButton.setOnAction(event -> stage.close());
} }
} }
@@ -6,7 +6,7 @@ import javafx.geometry.Point2D;
import javafx.scene.CacheHint; import javafx.scene.CacheHint;
import javafx.scene.Group; import javafx.scene.Group;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.paint.Paint; import javafx.scene.shape.Circle;
import javafx.scene.shape.Line; import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon; import javafx.scene.shape.Polygon;
import javafx.scene.transform.Rotate; import javafx.scene.transform.Rotate;
@@ -64,14 +64,31 @@ public class BoatObject extends Group {
* polygon. * polygon.
*/ */
public BoatObject(double... points) { public BoatObject(double... points) {
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 = new Polygon(points);
boatPoly.setFill(colour); boatPoly.setFill(colour);
boatPoly.setFill(this.color);
boatPoly.setOnMouseEntered(event -> { boatPoly.setOnMouseEntered(event -> {
boatPoly.setFill(Color.FLORALWHITE); boatPoly.setFill(Color.FLORALWHITE);
boatPoly.setStroke(Color.RED); boatPoly.setStroke(Color.RED);
}); });
boatPoly.setOnMouseExited(event -> { boatPoly.setOnMouseExited(event -> {
boatPoly.setFill(colour); boatPoly.setFill(colour);
boatPoly.setFill(this.color);
boatPoly.setStroke(Color.BLACK); boatPoly.setStroke(Color.BLACK);
}); });
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected)); boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
@@ -80,6 +97,7 @@ public class BoatObject extends Group {
annotationBox = new AnnotationBox(); annotationBox = new AnnotationBox();
annotationBox.setFill(colour); annotationBox.setFill(colour);
boatAnnotations = new BoatAnnotations(boat, this.color);
leftLayLine = new Line(); leftLayLine = new Line();
rightLayline = new Line(); rightLayline = new Line();
@@ -178,25 +196,44 @@ public class BoatObject extends Group {
*/ */
public void setDestination(double newXValue, double newYValue, double rotation, public void setDestination(double newXValue, double newYValue, double rotation,
double groundSpeed, long timeValid, double frameRate) { double groundSpeed, long timeValid, double frameRate) {
if (lastTimeValid == 0) {
lastTimeValid = timeValid - 200;
moveTo(newXValue, newYValue, rotation);
}
framesToMove = Math.round((frameRate / (1000.0f / (timeValid - lastTimeValid))));
double dx = newXValue - boatPoly.getLayoutX();
double dy = newYValue - boatPoly.getLayoutY();
xIncrement = dx / framesToMove;
yIncrement = dy / framesToMove;
destinationSet = true; destinationSet = true;
Double dx = Math.abs(boatPoly.getLayoutX() - newXValue);
Double dy = Math.abs(boatPoly.getLayoutY() - newYValue);
moveTo(newXValue, newYValue, rotation);
rotateTo(rotation); rotateTo(rotation);
wake.setRotation(rotation, groundSpeed); wake.setRotation(rotation, groundSpeed);
// yacht.setVelocity(groundSpeed); // yacht.setVelocity(groundSpeed);
lastTimeValid = timeValid; lastTimeValid = timeValid;
boat.setVelocity(groundSpeed);
isStopped = false; isStopped = false;
lastRotation = rotation; 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());
}
}
} }
@@ -243,6 +280,8 @@ public class BoatObject extends Group {
public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime, public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime,
boolean trail, boolean wake) { boolean trail, boolean wake) {
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.wake.setVisible(wake);
this.lineGroup.setVisible(trail); this.lineGroup.setVisible(trail);
} }
@@ -297,4 +336,23 @@ public class BoatObject extends Group {
return 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;
}
} }
@@ -54,29 +54,29 @@ public class Wake extends Group {
} }
void setRotation (double rotation, double velocity) { void setRotation (double rotation, double velocity) {
if (Math.abs(rotations[0] - rotation) > 20) { // if (Math.abs(rotations[0] - rotation) > 20) {
rotate(rotation); rotate(rotation);
} else { // } else {
rotations[0] = rotation; // rotations[0] = rotation;
((Rotate) arcs[0].getTransforms().get(0)).setAngle(rotation); // ((Rotate) arcs[0].getTransforms().get(0)).setAngle(rotation);
for (int i = 1; i < numWakes; i++) { // for (int i = 1; i < numWakes; i++) {
double wakeSeparationRad = Math.toRadians(rotations[i - 1] - rotations[i]); // double wakeSeparationRad = Math.toRadians(rotations[i - 1] - rotations[i]);
double shortestDistance = Math.atan2( // double shortestDistance = Math.atan2(
Math.sin(wakeSeparationRad), // Math.sin(wakeSeparationRad),
Math.cos(wakeSeparationRad) // Math.cos(wakeSeparationRad)
); // );
double distDeg = Math.toDegrees(shortestDistance); // double distDeg = Math.toDegrees(shortestDistance);
if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) { // if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) {
rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * 2 * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes); // rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * 2 * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
//
} else { // } else {
if (distDeg < (MAX_DIFF / numWakes)) { // if (distDeg < (MAX_DIFF / numWakes)) {
rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes); // rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
} else // } else
rotationalVelocities[i] = rotationalVelocities[i - 1]; // rotationalVelocities[i] = rotationalVelocities[i - 1];
} // }
} // }
} // }
double rad = (14 / numWakes) + velocity; double rad = (14 / numWakes) + velocity;
for (Arc arc : arcs) { for (Arc arc : arcs) {
+1 -1
View File
@@ -1,4 +1,4 @@
Tws,Twa0,Bsp0,Twa1,Bsp1,UpTwa,UpBsp,Twa2,Bsp2,Twa3,Bsp3,Twa4,Bsp4,Twa5,Bsp5,Twa6,Bsp6,DnTwa,DnBsp,Twa7,Bsp7 Tws,Twa0,Bsp0,Twa1,Bsp1,UpTwa,UpBsp,Twa2,Bsp2,Twa3,Bsp3,Twa4,Bsp4,Twa5,Bsp5,Twa6,Bsp6,DnTwa,DnBsp,Twa7,Bsp7
4,0,0,30,4,45,8,60,9,75,10,90,10,115,10,145,10,155,10,175,4 4,0,0,30,4,45,8,60,9,75,10,90,10,115,10,145,10,155,10,175,4
8,0,0,30,7,43,10,60,11,75,11,90,11,115,12,145,12,153,12,175,10 8,0,0,30,7,43,10,60,11,75,11,90,11,115,12,145,12,153,12,175,10
12,0,0,30,11,43,14.4,60,16,75,20,90,23,115,24,145,23,153,21.6,175,14 12,0,0,30,11,43,14.4,60,16,75,20,90,23,115,24,145,23,153,21.6,175,14
1 Tws Twa0 Bsp0 Twa1 Bsp1 UpTwa UpBsp Twa2 Bsp2 Twa3 Bsp3 Twa4 Bsp4 Twa5 Bsp5 Twa6 Bsp6 DnTwa DnBsp Twa7 Bsp7
2 4 0 0 30 4 45 8 60 9 75 10 90 10 115 10 145 10 155 10 175 4
3 8 0 0 30 7 43 10 60 11 75 11 90 11 115 12 145 12 153 12 175 10
4 12 0 0 30 11 43 14.4 60 16 75 20 90 23 115 24 145 23 153 21.6 175 14
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10 -10
View File
@@ -20,31 +20,31 @@
<children> <children>
<AnchorPane prefHeight="960.0" prefWidth="250.0" style="-fx-background-color: #2C2c36;" GridPane.rowSpan="3"> <AnchorPane prefHeight="960.0" prefWidth="250.0" style="-fx-background-color: #2C2c36;" GridPane.rowSpan="3">
<children> <children>
<Label layoutX="11.0" layoutY="259.0" text="Team Position" textFill="WHITE" /> <Label layoutX="11.0" layoutY="283.0" text="Team Position" textFill="WHITE" />
<Label layoutX="13.0" layoutY="432.0" text="Settings" textFill="WHITE" /> <Label layoutX="13.0" layoutY="432.0" text="Settings" textFill="WHITE" />
<Label layoutX="11.0" layoutY="14.0" text="Timer" textFill="WHITE" /> <Label layoutX="11.0" layoutY="41.0" text="Timer" textFill="WHITE" />
<Label layoutX="11.0" layoutY="88.0" text="Wind direction" textFill="WHITE" /> <Label layoutX="11.0" layoutY="112.0" text="Wind direction" textFill="WHITE" />
<Circle fx:id="windBackgroundCircle" blendMode="DARKEN" fill="#3dcdc8" layoutX="110.0" layoutY="166.0" radius="35.0" stroke="#d7d7d7" strokeType="INSIDE" strokeWidth="3.0" /> <Circle fx:id="windBackgroundCircle" blendMode="DARKEN" fill="#3dcdc8" layoutX="110.0" layoutY="190.0" radius="35.0" stroke="#d7d7d7" strokeType="INSIDE" strokeWidth="3.0" />
<Text fx:id="windArrowText" fill="#a8a8a8" layoutX="86.0" layoutY="186.0" strokeType="OUTSIDE" strokeWidth="0.0" text="↓"> <Text fx:id="windArrowText" fill="#a8a8a8" layoutX="86.0" layoutY="210.0" strokeType="OUTSIDE" strokeWidth="0.0" text="↓">
<font> <font>
<Font name="AdobeArabic-Regular" size="55.0" /> <Font name="AdobeArabic-Regular" size="55.0" />
</font> </font>
</Text> </Text>
<Text fx:id="windDirectionText" fill="#d3d3d3" layoutX="171.0" layoutY="214.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0°" textAlignment="RIGHT"> <Text fx:id="windDirectionText" fill="#d3d3d3" layoutX="171.0" layoutY="238.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0°" textAlignment="RIGHT">
<font> <font>
<Font name="System Bold" size="13.0" /> <Font name="System Bold" size="13.0" />
</font> </font>
</Text> </Text>
<Text fx:id="windSpeedText" fill="#d3d3d3" layoutX="12.0" layoutY="213.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0 Knot" textAlignment="RIGHT"> <Text fx:id="windSpeedText" fill="#d3d3d3" layoutX="12.0" layoutY="237.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0 Knot" textAlignment="RIGHT">
<font> <font>
<Font name="System Bold" size="13.0" /> <Font name="System Bold" size="13.0" />
</font> </font>
</Text> </Text>
<CheckBox fx:id="toggleFps" focusTraversable="false" graphicTextGap="0.0" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" styleClass="ui-checkbox" text="Show FPS" textFill="WHITE" /> <CheckBox fx:id="toggleFps" focusTraversable="false" graphicTextGap="0.0" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" styleClass="ui-checkbox" text="Show FPS" textFill="WHITE" />
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" styleClass="text-white" /> <VBox fx:id="positionVbox" layoutX="12.0" layoutY="304.0" prefHeight="116.0" prefWidth="200.0" styleClass="text-white" />
<Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0"> <Pane layoutX="11.0" layoutY="39.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0">
<children> <children>
<Text fx:id="timerLabel" fill="#f8f8f8" layoutX="-26.0" layoutY="34.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="246.0"> <Text fx:id="timerLabel" fill="#f8f8f8" layoutX="-26.0" layoutY="51.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="246.0">
<font> <font>
<Font size="25.0" /> <Font size="25.0" />
</font> </font>
@@ -24,4 +24,5 @@ public class YachtTest {
yachts.add(new Yacht("Yacht 3", "Y3", new GeoPoint(-35.0, -15.5), 20.0)); yachts.add(new Yacht("Yacht 3", "Y3", new GeoPoint(-35.0, -15.5), 20.0));
} }
} }