diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 00000000..f0fdafc3 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,17 @@ +engines: + pmd: + enabled: true + channel: "beta" + + fixme: + enabled: true + config: + strings: + - FIXME + - TODO + - BUG + - FIX + +ratings: + paths: + - "**.java" diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index c796998a..7069d719 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -4,76 +4,37 @@ import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; +import javafx.scene.image.Image; import javafx.stage.Stage; +import seng302.client.ClientPacketParser; import seng302.models.PolarTable; -import seng302.models.stream.StreamParser; import seng302.models.stream.StreamReceiver; -import seng302.server.ServerThread; public class App extends Application { @Override public void start(Stage primaryStage) throws Exception { - PolarTable.parsePolarFile(getClass().getResource("/config/acc_polars.csv").getFile()); + PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml")); primaryStage.setTitle("RaceVision"); primaryStage.setScene(new Scene(root, 1530, 960)); primaryStage.setMaxWidth(1530); primaryStage.setMaxHeight(960); + primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png"))); // primaryStage.setMaximized(true); primaryStage.show(); primaryStage.setOnCloseRequest(e -> { - StreamParser.appClose(); + ClientPacketParser.appClose(); StreamReceiver.noMoreBytes(); System.exit(0); }); + } public static void main(String[] args) { - StreamReceiver sr = null; - - new ServerThread("Racevision Test Server"); - - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (args.length == 1 && args[0].equals("-standalone")) { - return; - } - - if (args.length == 3 && args[0].equals("-server")) { - - sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream"); - - } else if (args.length == 2 && args[0].equals("-server")) { - switch (args[1]) { - case "internal": - sr = new StreamReceiver("localhost", 4949, "RaceStream"); - break; - case "staffserver": - sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); - break; - case "official": - sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); - break; - } - } - //Change the StreamReceiver in this else block to change the default data source. - else{ - sr = new StreamReceiver("localhost", 4949, "RaceStream"); - } - - sr.start(); - StreamParser streamParser = new StreamParser("StreamParser"); - streamParser.start(); - launch(args); - } } diff --git a/src/main/java/seng302/models/stream/StreamParser.java b/src/main/java/seng302/client/ClientPacketParser.java similarity index 94% rename from src/main/java/seng302/models/stream/StreamParser.java rename to src/main/java/seng302/client/ClientPacketParser.java index e7286561..2cae4857 100644 --- a/src/main/java/seng302/models/stream/StreamParser.java +++ b/src/main/java/seng302/client/ClientPacketParser.java @@ -1,4 +1,4 @@ -package seng302.models.stream; +package seng302.client; import java.io.IOException; @@ -11,7 +11,6 @@ import java.util.Comparator; import java.util.Date; import java.util.Map; import java.util.TimeZone; -import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.PriorityBlockingQueue; @@ -23,6 +22,7 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; import seng302.models.Yacht; import seng302.models.mark.Mark; +import seng302.models.stream.XMLParser; import seng302.models.stream.packets.BoatPositionPacket; import seng302.models.stream.packets.StreamPacket; @@ -32,7 +32,7 @@ import seng302.models.stream.packets.StreamPacket; * that are threadsafe so the visualiser can always access the latest speed and position available * Created by kre39 on 23/04/17. */ -public class StreamParser extends Thread { +public class ClientPacketParser { public static ConcurrentHashMap> markLocations = new ConcurrentHashMap<>(); public static ConcurrentHashMap> boatLocations = new ConcurrentHashMap<>(); @@ -58,54 +58,16 @@ public class StreamParser extends Thread { /** * Used to initialise the thread name and stream parser object so a thread can be executed - * - * @param threadName name of the thread */ - public StreamParser(String threadName) { - this.threadName = threadName; + public ClientPacketParser() { } - - /** - * Used to within threading so when the stream parser thread runs, it will keep looking for a - * packet to process until it is unable to find anymore packets - */ - public void run() { - appRunning = true; - try { - streamStatus = true; - xmlObject = new XMLParser(); - while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) { - Thread.sleep(1); - } - while (appRunning) { - StreamPacket packet = StreamReceiver.packetBuffer.take(); - parsePacket(packet); - Thread.sleep(1); - while (StreamReceiver.packetBuffer.peek() == null) { - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Used to start the stream parser thread when multithreading - */ - public void start() { - if (t == null) { - t = new Thread(this, threadName); - t.start(); - } - } - /** * Looks at the type of the packet then sends it to the appropriate parser to extract the * specific data associated with that packet type * * @param packet the packet to be looked at and processed */ - private static void parsePacket(StreamPacket packet) { + public static void parsePacket(StreamPacket packet) { try { switch (packet.getType()) { case HEARTBEAT: @@ -145,8 +107,6 @@ public class StreamParser extends Thread { case AVG_WIND: extractAvgWind(packet); break; - default: - break; } } catch (NullPointerException e) { System.out.println("Error parsing packet"); @@ -161,6 +121,7 @@ public class StreamParser extends Thread { */ private static void extractHeartBeat(StreamPacket packet) { long heartbeat = bytesToLong(packet.getPayload()); + System.out.println("heartbeat = " + heartbeat); } private static String getTimeZoneString() { @@ -392,6 +353,7 @@ public class StreamParser extends Thread { int messageType = payload[1]; int length = payload[2]; String message = new String(Arrays.copyOfRange(payload, 3, 3 + length)); + System.out.println(message); } /** diff --git a/src/main/java/seng302/client/ClientToServerThread.java b/src/main/java/seng302/client/ClientToServerThread.java new file mode 100644 index 00000000..e2c1cff9 --- /dev/null +++ b/src/main/java/seng302/client/ClientToServerThread.java @@ -0,0 +1,141 @@ +package seng302.client; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.zip.CRC32; +import java.util.zip.Checksum; + +import seng302.models.stream.packets.StreamPacket; +import seng302.server.messages.BoatActionMessage; +import seng302.server.messages.Message; + +/** + * Created by kre39 on 13/07/17. + */ +public class ClientToServerThread extends Thread { + private Socket socket; + private InputStream is; + private OutputStream os; + private final int PORT_NUMBER = 0; + private static final int LOG_LEVEL = 1; + + private Boolean updateClient = true; + private ByteArrayOutputStream crcBuffer; + + public ClientToServerThread(String ipAddress, Integer portNumber){ + try { + socket = new Socket(ipAddress, portNumber); + is = socket.getInputStream(); + os = socket.getOutputStream(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + static void serverLog(String message, int logLevel){ + if(logLevel <= LOG_LEVEL){ + System.out.println("[SERVER] " + message); + } + } + + public void run() { + int sync1; + int sync2; + // TODO: 14/07/17 wmu16 - Work out how to fix this while loop + while(true) { + try { + //Perform a write if it is time to as delegated by the MainServerThread + if (updateClient) { + // TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream +// try { +// GameState.outputState(os); +// } catch (IOException e) { +// System.out.println("IO error in server thread upon writing to output stream"); +// } + updateClient = false; + } + crcBuffer = new ByteArrayOutputStream(); + sync1 = readByte(); + sync2 = readByte(); + //checking if it is the start of the packet + if(sync1 == 0x47 && sync2 == 0x83) { + int type = readByte(); + //No. of milliseconds since Jan 1st 1970 + long timeStamp = Message.bytesToLong(getBytes(6)); + skipBytes(4); + long payloadLength = Message.bytesToLong(getBytes(2)); + byte[] payload = getBytes((int) payloadLength); + Checksum checksum = new CRC32(); + checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size()); + long computedCrc = checksum.getValue(); + long packetCrc = Message.bytesToLong(getBytes(4)); + if (computedCrc == packetCrc) { + ClientPacketParser + .parsePacket(new StreamPacket(type, payloadLength, timeStamp, payload)); + // TODO: 17/07/17 wmu16 - Fix this or maybe we dont need to go through the main server at all!?!? +// packetBufferDelegate.addToBuffer(new StreamPacket(type, payloadLength, timeStamp, payload)); + } else { + System.err.println("Packet has been dropped"); + } + } + } catch (Exception e) { + closeSocket(); + return; + } + } + + } + + /** + * Send the post-start race course information + */ + public void sendBoatActionMessage(BoatActionMessage boatActionMessage) { + try { + os.write(boatActionMessage.getBuffer()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + public void closeSocket() { + try { + socket.close(); + } catch (IOException e) { + System.out.println("IO error in server thread upon trying to close socket"); + } + } + + + private int readByte() throws Exception { + int currentByte = -1; + try { + currentByte = is.read(); + crcBuffer.write(currentByte); + } catch (IOException e) { + e.printStackTrace(); + } + if (currentByte == -1){ + throw new Exception(); + } + return currentByte; + } + + private byte[] getBytes(int n) throws Exception{ + byte[] bytes = new byte[n]; + for (int i = 0; i < n; i++){ + bytes[i] = (byte) readByte(); + } + return bytes; + } + + private void skipBytes(long n) throws Exception{ + for (int i=0; i < n; i++){ + readByte(); + } + } + } diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 7f40b243..5c69f846 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -21,6 +21,7 @@ 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.fxObjects.BoatGroup; import seng302.models.Colors; import seng302.models.Yacht; @@ -31,7 +32,6 @@ import seng302.models.mark.MarkType; import seng302.models.mark.SingleMark; import seng302.models.map.Boundary; import seng302.models.map.CanvasMap; -import seng302.models.stream.StreamParser; import seng302.models.stream.XMLParser; import seng302.models.stream.XMLParser.RaceXMLObject.Limit; import seng302.models.stream.XMLParser.RaceXMLObject.Participant; @@ -110,6 +110,7 @@ public class CanvasController { // Bind canvas size to stack pane size. canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH)); canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT)); + } public void initializeCanvas() { @@ -154,13 +155,13 @@ public class CanvasController { raceViewController.updateSparkLine(); } updateGroups(); - if (StreamParser.isRaceFinished()) { + if (ClientPacketParser.isRaceFinished()) { this.stop(); } lastTime = now; } } - if (StreamParser.isRaceFinished()) { + if (ClientPacketParser.isRaceFinished()) { this.stop(); switchToFinishScreen(); } @@ -229,7 +230,7 @@ public class CanvasController { * in a compound mark etc.. */ private void addRaceBorder() { - XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML(); + XMLParser.RaceXMLObject raceXMLObject = ClientPacketParser.getXmlObject().getRaceXML(); ArrayList courseLimits = raceXMLObject.getCourseLimit(); raceBorder.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1)); raceBorder.setStrokeWidth(3); @@ -247,7 +248,7 @@ public class CanvasController { for (BoatGroup boatGroup : boatGroups) { // some raceObjects will have multiple ID's (for instance gate marks) //checking if the current "ID" has any updates associated with it - if (StreamParser.boatLocations.containsKey(boatGroup.getRaceId())) { + if (ClientPacketParser.boatLocations.containsKey(boatGroup.getRaceId())) { if (boatGroup.isStopped()) { updateBoatGroup(boatGroup); } @@ -256,7 +257,7 @@ public class CanvasController { } for (MarkGroup markGroup : markGroups) { for (Long id : markGroup.getRaceIds()) { - if (StreamParser.markLocations.containsKey(id)) { + if (ClientPacketParser.markLocations.containsKey(id)) { updateMarkGroup(id, markGroup); } } @@ -265,13 +266,14 @@ public class CanvasController { } private void checkForCourseChanges() { - if (StreamParser.isNewRaceXmlReceived()){ + if (ClientPacketParser.isNewRaceXmlReceived()) { addRaceBorder(); } } private void updateBoatGroup(BoatGroup boatGroup) { - PriorityBlockingQueue movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId()); + PriorityBlockingQueue movementQueue = ClientPacketParser.boatLocations + .get(boatGroup.getRaceId()); // giving the movementQueue a 5 packet buffer to account for slightly out of order packets if (movementQueue.size() > 0) { try { @@ -289,7 +291,8 @@ public class CanvasController { } void updateMarkGroup (long raceId, MarkGroup markGroup) { - PriorityBlockingQueue movementQueue = StreamParser.markLocations.get(raceId); + PriorityBlockingQueue movementQueue = ClientPacketParser.markLocations + .get(raceId); if (movementQueue.size() > 0){ try { BoatPositionPacket positionPacket = movementQueue.take(); @@ -305,12 +308,12 @@ public class CanvasController { * Draws all the boats. */ private void initializeBoats() { - Map boats = StreamParser.getBoats(); + Map boats = ClientPacketParser.getBoats(); Group wakes = new Group(); Group trails = new Group(); Group annotations = new Group(); - ArrayList participants = StreamParser.getXmlObject().getRaceXML() + ArrayList participants = ClientPacketParser.getXmlObject().getRaceXML() .getParticipants(); ArrayList participantIDs = new ArrayList<>(); for (Participant p : participants) { @@ -334,7 +337,8 @@ public class CanvasController { } private void initializeMarks() { - List allMarks = StreamParser.getXmlObject().getRaceXML().getNonDupCompoundMarks(); + List allMarks = ClientPacketParser.getXmlObject().getRaceXML() + .getNonDupCompoundMarks(); for (Mark mark : allMarks) { if (mark.getMarkType() == MarkType.SINGLE_MARK) { SingleMark sMark = (SingleMark) mark; @@ -400,7 +404,7 @@ public class CanvasController { */ private void fitMarksToCanvas() { //Check is called once to avoid unnecessarily change the course limits once the race is running - StreamParser.isNewRaceXmlReceived(); + ClientPacketParser.isNewRaceXmlReceived(); findMinMaxPoint(); double minLonToMaxLon = scaleRaceExtremities(); calculateReferencePointLocation(minLonToMaxLon); @@ -416,7 +420,7 @@ public class CanvasController { */ private void findMinMaxPoint() { List sortedPoints = new ArrayList<>(); - for (Limit limit : StreamParser.getXmlObject().getRaceXML().getCourseLimit()) { + for (Limit limit : ClientPacketParser.getXmlObject().getRaceXML().getCourseLimit()) { sortedPoints.add(limit); } sortedPoints.sort(Comparator.comparingDouble(Limit::getLat)); @@ -576,4 +580,5 @@ public class CanvasController { List getMarkGroups() { return markGroups; } + } \ No newline at end of file diff --git a/src/main/java/seng302/controllers/Controller.java b/src/main/java/seng302/controllers/Controller.java index 73b3766b..5ca31ee7 100644 --- a/src/main/java/seng302/controllers/Controller.java +++ b/src/main/java/seng302/controllers/Controller.java @@ -1,39 +1,91 @@ package seng302.controllers; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.fxml.Initializable; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.Pane; - import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; -import seng302.models.stream.StreamParser; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Parent; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.AnchorPane; +import seng302.client.ClientPacketParser; +import seng302.client.ClientToServerThread; +import seng302.server.messages.BoatActionMessage; +import seng302.server.messages.BoatActionType; public class Controller implements Initializable { @FXML private AnchorPane contentPane; + private ClientToServerThread clientToServerThread; - private void setContentPane(String jfxUrl) { + private Object setContentPane(String jfxUrl) { try { contentPane.getChildren().removeAll(); contentPane.getChildren().clear(); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - contentPane.getChildren() - .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); + FXMLLoader fxmlLoader = new FXMLLoader((getClass().getResource(jfxUrl))); + Parent view = fxmlLoader.load(); + contentPane.getChildren().addAll(view); + return fxmlLoader.getController(); } catch (javafx.fxml.LoadException e) { System.err.println(e.getCause()); } catch (IOException e) { System.err.println(e); } + return null; } @Override public void initialize(URL location, ResourceBundle resources) { contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - setContentPane("/views/StartScreenView.fxml"); - StreamParser.boatLocations.clear(); + StartScreenController startScreenController = (StartScreenController) setContentPane("/views/StartScreenView.fxml"); + startScreenController.setController(this); + ClientPacketParser.boatLocations.clear(); + } + + /** Handle the key-pressed event from the text field. */ + public void keyPressed(KeyEvent e) { + BoatActionMessage boatActionMessage; + switch (e.getCode()){ + case SPACE: // align with vmg + boatActionMessage = new BoatActionMessage(BoatActionType.VMG); + clientToServerThread.sendBoatActionMessage(boatActionMessage); + break; + case PAGE_UP: // upwind + boatActionMessage = new BoatActionMessage(BoatActionType.UPWIND); + clientToServerThread.sendBoatActionMessage(boatActionMessage); + break; + case PAGE_DOWN: // downwind + boatActionMessage = new BoatActionMessage(BoatActionType.DOWNWIND); + clientToServerThread.sendBoatActionMessage(boatActionMessage); + break; + case ENTER: // tack/gybe + boatActionMessage = new BoatActionMessage(BoatActionType.TACK_GYBE); + clientToServerThread.sendBoatActionMessage(boatActionMessage); + break; + //TODO Allow a zoom in and zoom out methods + case Z: // zoom in + System.out.println("Zoom in"); + break; + case X: // zoom out + System.out.println("Zoom out"); + break; + } + } + + public void keyReleased(KeyEvent e) { + switch (e.getCode()) { + //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet) + case SHIFT: // sails in/sails out + BoatActionMessage boatActionMessage = new BoatActionMessage(BoatActionType.SAILS_IN); + clientToServerThread.sendBoatActionMessage(boatActionMessage); + break; + } + } + + public void setClientToServerThread(ClientToServerThread ctt) { + clientToServerThread = ctt; } } diff --git a/src/main/java/seng302/controllers/FinishScreenViewController.java b/src/main/java/seng302/controllers/FinishScreenViewController.java index a2d79f36..1491c7c9 100644 --- a/src/main/java/seng302/controllers/FinishScreenViewController.java +++ b/src/main/java/seng302/controllers/FinishScreenViewController.java @@ -15,8 +15,8 @@ import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; +import seng302.client.ClientPacketParser; import seng302.models.Yacht; -import seng302.models.stream.StreamParser; import seng302.models.stream.XMLParser.RaceXMLObject.Participant; public class FinishScreenViewController implements Initializable { @@ -59,7 +59,7 @@ public class FinishScreenViewController implements Initializable { ); // check if the boat is racing - ArrayList participants = StreamParser.getXmlObject().getRaceXML() + ArrayList participants = ClientPacketParser.getXmlObject().getRaceXML() .getParticipants(); ArrayList participantIDs = new ArrayList<>(); for (Participant p : participants) { @@ -67,7 +67,7 @@ public class FinishScreenViewController implements Initializable { } // add data to table - for (Yacht boat : StreamParser.getBoatsPos().values()) { + for (Yacht boat : ClientPacketParser.getBoatsPos().values()) { if (participantIDs.contains(boat.getSourceID())) { data.add(boat); } diff --git a/src/main/java/seng302/controllers/LobbyController.java b/src/main/java/seng302/controllers/LobbyController.java new file mode 100644 index 00000000..565812cc --- /dev/null +++ b/src/main/java/seng302/controllers/LobbyController.java @@ -0,0 +1,109 @@ +package seng302.controllers; + +import java.io.IOException; +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.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.control.ListView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +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 implements Initializable{ + + @FXML + private ListView competitorsListView; + @FXML + private GridPane lobbyScreen; + @FXML + private Text lobbyIpText; + + private static ObservableList competitors; + + private void setContentPane(String jfxUrl) { + try { + AnchorPane contentPane = (AnchorPane) lobbyScreen.getParent(); + contentPane.getChildren().removeAll(); + contentPane.getChildren().clear(); + contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + contentPane.getChildren() + .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); + } catch (javafx.fxml.LoadException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + lobbyIpText.setText("Lobby Host IP: " + getLocalHostIp()); + } + + public void initialize() { + competitors = FXCollections.observableArrayList(); + competitorsListView.setItems(competitors); + } + + private String getLocalHostIp() { + String ipAddress = null; + try { + Enumeration e = NetworkInterface.getNetworkInterfaces(); + while (e.hasMoreElements()) { + NetworkInterface ni = e.nextElement(); + if (ni.isLoopback()) + continue; + if(ni.isPointToPoint()) + continue; + if(ni.isVirtual()) + continue; + + Enumeration addresses = ni.getInetAddresses(); + while(addresses.hasMoreElements()) { + InetAddress address = addresses.nextElement(); + if(address instanceof Inet4Address) { // skip all ipv6 + ipAddress = address.getHostAddress(); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + if (ipAddress == null) { + System.out.println("[HOST] Cannot obtain local host ip address."); + } + return ipAddress; + } + + @FXML + public void leaveLobbyButtonPressed() { + // TODO: 10/07/17 wmu16 - Finish function! + setContentPane("/views/StartScreenView.fxml"); + System.out.println("Leaving lobby!"); + GameState.setCurrentStage(GameStages.CANCELLED); + // TODO: 20/07/17 wmu16 - Implement some way of terminating the game + } + + + @FXML + public void readyButtonPressed() { + GameState.setCurrentStage(GameStages.RACING); + setContentPane("/views/RaceView.fxml"); + + } +} diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 8b30113c..0fa8b17c 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -27,7 +27,8 @@ import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.util.Duration; import javafx.util.StringConverter; -import seng302.utilities.GeometryUtils; +import seng302.client.ClientPacketParser; +import seng302.utilities.GeoUtility; import seng302.controllers.annotations.Annotation; import seng302.controllers.annotations.ImportantAnnotationController; import seng302.controllers.annotations.ImportantAnnotationDelegate; @@ -38,7 +39,6 @@ import seng302.models.*; import seng302.models.mark.GateMark; import seng302.models.mark.Mark; import seng302.models.mark.SingleMark; -import seng302.models.stream.StreamParser; import seng302.models.stream.XMLParser; import java.io.IOException; @@ -93,7 +93,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel raceSparkLine.getYAxis().setTranslateX(-5); raceSparkLine.getYAxis().setAutoRanging(false); sparklineYAxis.setTickMarkVisible(false); - startingBoats = new ArrayList<>(StreamParser.getBoats().values()); + startingBoats = new ArrayList<>(ClientPacketParser.getBoats().values()); includedCanvasController.setup(this); includedCanvasController.initializeCanvas(); @@ -103,6 +103,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel initialiseBoatSelectionComboBox(); includedCanvasController.timer.start(); selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); + } @@ -136,7 +137,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel Scene scene = new Scene(fxmlLoader.load(), 469, 298); scene.getStylesheets().add(getClass().getResource("/css/master.css").toString()); stage.initStyle(StageStyle.UNDECORATED); - stage.setScene(scene); stage.show(); @@ -303,7 +303,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel private Mark getNextMark(BoatGroup bg) { Integer legNumber = bg.getBoat().getLegNumber(); - List markSequence = StreamParser.getXmlObject().getRaceXML().getCompoundMarkSequence(); + List markSequence = ClientPacketParser.getXmlObject() + .getRaceXML().getCompoundMarkSequence(); if (legNumber == 0) { return null; @@ -315,7 +316,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel if (legNumber + 2 == corner.getSeqID()) { Integer thisCompoundMarkID = corner.getCompoundMarkID(); - for (Mark mark : StreamParser.getXmlObject().getRaceXML().getAllCompoundMarks()) { + for (Mark mark : ClientPacketParser.getXmlObject().getRaceXML() + .getAllCompoundMarks()) { if (mark.getCompoundMarkID() == thisCompoundMarkID) { return mark; } @@ -328,11 +330,11 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel /** - * Updates the wind direction arrow and text as from info from the StreamParser + * Updates the wind direction arrow and text as from info from the ClientPacketParser */ private void updateWindDirection() { - windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection())); - windArrowText.setRotate(StreamParser.getWindDirection()); + windDirectionText.setText(String.format("%.1f°", ClientPacketParser.getWindDirection())); + windArrowText.setRotate(ClientPacketParser.getWindDirection()); } @@ -340,7 +342,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel * Updates the clock for the race */ private void updateRaceTime() { - if (StreamParser.isRaceFinished()) { + if (ClientPacketParser.isRaceFinished()) { timerLabel.setFill(Color.RED); timerLabel.setText("Race Finished!"); } else { @@ -350,18 +352,18 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel /** - * Grabs the boats currently in the race as from the StreamParser and sets them to be selectable + * Grabs the boats currently in the race as from the ClientPacketParser and sets them to be selectable * in the boat selection combo box */ private void updateBoatSelectionComboBox() { ObservableList observableBoats = FXCollections - .observableArrayList(StreamParser.getBoatsPos().values()); + .observableArrayList(ClientPacketParser.getBoatsPos().values()); boatSelectionComboBox.setItems(observableBoats); } /** - * Updates the order of the boats as from the StreamParser and sets them in the boat order + * Updates the order of the boats as from the ClientPacketParser and sets them in the boat order * section */ private void updateOrder() { @@ -370,15 +372,15 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); // list of racing boat id - ArrayList participants = StreamParser.getXmlObject().getRaceXML() + ArrayList participants = ClientPacketParser.getXmlObject().getRaceXML() .getParticipants(); ArrayList participantIDs = new ArrayList<>(); for (Participant p : participants) { participantIDs.add(p.getsourceID()); } - if (StreamParser.isRaceStarted()) { - for (Yacht boat : StreamParser.getBoatsPos().values()) { + if (ClientPacketParser.isRaceStarted()) { + for (Yacht boat : ClientPacketParser.getBoatsPos().values()) { if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing if (boat.getBoatStatus() == 3) { // 3 is finish status Text textToAdd = new Text(boat.getPosition() + ". " + @@ -396,7 +398,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } } } else { - for (Yacht boat : StreamParser.getBoats().values()) { + 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() + " "); @@ -434,9 +436,11 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel Point2D markPoint2 = includedCanvasController.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude()); HashMap angleAndSpeed; if (isUpwind) { - angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed()); + angleAndSpeed = PolarTable + .getOptimalUpwindVMG(ClientPacketParser.getWindSpeed()); } else { - angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed()); + angleAndSpeed = PolarTable + .getOptimalDownwindVMG(ClientPacketParser.getWindSpeed()); } Double resultingAngle = angleAndSpeed.keySet().iterator().next(); @@ -444,15 +448,23 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY()); Point2D gateMidPoint = markPoint1.midpoint(markPoint2); - Integer lineFuncResult = GeometryUtils.lineFunction(boatCurrentPos, gateMidPoint, markPoint2); + Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, gateMidPoint, markPoint2); Line rightLayline = new Line(); Line leftLayline = new Line(); if (lineFuncResult == 1) { - rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection()); - leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection()); + rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, + ClientPacketParser + .getWindDirection()); + leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, + ClientPacketParser + .getWindDirection()); } else if (lineFuncResult == -1) { - rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection()); - leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection()); + rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, + ClientPacketParser + .getWindDirection()); + leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, + ClientPacketParser + .getWindDirection()); } leftLayline.setStrokeWidth(0.5); @@ -548,16 +560,16 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel private String getTimeSinceStartOfRace() { String timerString = "0:00"; - if (StreamParser.getTimeSinceStart() > 0) { - String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60); + if (ClientPacketParser.getTimeSinceStart() > 0) { + String timerMinute = Long.toString(ClientPacketParser.getTimeSinceStart() / 60); + String timerSecond = Long.toString(ClientPacketParser.getTimeSinceStart() % 60); if (timerSecond.length() == 1) { timerSecond = "0" + timerSecond; } timerString = "-" + timerMinute + ":" + timerSecond; } else { - String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60); + String timerMinute = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() / 60); + String timerSecond = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() % 60); if (timerSecond.length() == 1) { timerSecond = "0" + timerSecond; } @@ -637,4 +649,5 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel public static boolean sparkLineStatus(Integer yachtId) { return sparkLineData.containsKey(yachtId); } + } \ No newline at end of file diff --git a/src/main/java/seng302/controllers/StartScreenController.java b/src/main/java/seng302/controllers/StartScreenController.java index 931874d5..6f9a094c 100644 --- a/src/main/java/seng302/controllers/StartScreenController.java +++ b/src/main/java/seng302/controllers/StartScreenController.java @@ -1,191 +1,97 @@ package seng302.controllers; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.ResourceBundle; -import java.util.Timer; -import java.util.TimerTask; -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; -import javafx.fxml.Initializable; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.control.TextField; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import seng302.models.Yacht; -import seng302.models.stream.StreamParser; -import seng302.models.stream.XMLParser.RaceXMLObject.Participant; +import seng302.client.ClientToServerThread; +import seng302.gameServer.GameState; +import seng302.gameServer.MainServerThread; -public class StartScreenController implements Initializable { +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * A Class describing the actions of the start screen controller + * Created by wmu16 on 10/07/17. + */ +public class StartScreenController { @FXML - private GridPane gridPane; + private TextField ipTextField; @FXML - private Label timeTillLive; - @FXML - private Button streamButton; - @FXML - private Button switchToRaceViewButton; - @FXML - private TableView teamList; - @FXML - private TableColumn boatNameCol; - @FXML - private TableColumn shortNameCol; - @FXML - private TableColumn countryCol; - @FXML - private TableColumn posCol; - @FXML - private Label realTime; + private GridPane startScreen2; - private boolean switchedToRaceView = false; + private Controller controller; - private void setContentPane(String jfxUrl) { + /** + * Loads the fxml content into the parent pane + * @param jfxUrl + * @return the controller of the fxml + */ + private Object setContentPane(String jfxUrl) { try { - // get the main controller anchor pane (MainView.fxml) - AnchorPane contentPane = (AnchorPane) gridPane.getParent(); + AnchorPane contentPane = (AnchorPane) startScreen2.getParent(); contentPane.getChildren().removeAll(); contentPane.getChildren().clear(); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - contentPane.getChildren() - .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl)); + contentPane.getChildren().addAll((Pane) fxmlLoader.load()); + return fxmlLoader.getController(); } catch (javafx.fxml.LoadException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } + return null; } - @Override - public void initialize(URL location, ResourceBundle resources) { - gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - } /** - * Running a timer to update the livestream status on welcome screen. Update interval is 1 - * second. + * 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 + * Switches to the lobby screen */ - public void startStream() { - // reset boolean for switch to race view - switchedToRaceView = false; + @FXML + public void hostButtonPressed() { + try { + String ipAddress = InetAddress.getLocalHost().getHostAddress(); + new GameState(ipAddress); + new MainServerThread().start(); + ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950); + controller.setClientToServerThread(clientToServerThread); + clientToServerThread.start(); +// new GameServerThread("Fuck you"); + // get the lobby controller so that we can pass the game server thread to it + setContentPane("/views/LobbyView.fxml"); - if (StreamParser.isStreamStatus()) { - streamButton.setVisible(false); - realTime.setVisible(true); - timeTillLive.setVisible(true); - timeTillLive.setTextFill(Color.GREEN); - timeTillLive.setText("Connecting..."); - Timer timer = new Timer(); - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - Platform.runLater(() -> { - if (StreamParser.isRaceStarted()) { - if (!switchedToRaceView) { - switchToRaceView(); - } - timer.cancel(); - } - if (StreamParser.isRaceFinished()) { - realTime.setText(StreamParser.getCurrentTimeString()); - timeTillLive.setTextFill(Color.RED); - timeTillLive.setText("Race finished! Waiting for new race..."); - switchToRaceViewButton.setDisable(true); - } else if (StreamParser.getTimeSinceStart() > 0) { - realTime.setText(StreamParser.getCurrentTimeString()); - updateTeamList(); - timeTillLive.setTextFill(Color.RED); - switchToRaceViewButton.setDisable(false); - String timerMinute = Long - .toString(StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long - .toString(StreamParser.getTimeSinceStart() % 60); - if (timerSecond.length() == 1) { - timerSecond = "0" + timerSecond; - } - String timerString = "-" + timerMinute + ":" + timerSecond; - timeTillLive.setText(timerString); - } else { - realTime.setText(StreamParser.getCurrentTimeString()); - updateTeamList(); - timeTillLive.setTextFill(Color.BLACK); - switchToRaceViewButton.setDisable(false); - String timerMinute = Long - .toString(-1 * StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long - .toString(-1 * StreamParser.getTimeSinceStart() % 60); - if (timerSecond.length() == 1) { - timerSecond = "0" + timerSecond; - } - String timerString = timerMinute + ":" + timerSecond; - timeTillLive.setText(timerString); - } - }); - } - }, 0, 1000); - } else { - timeTillLive.setText("Stream not available."); - timeTillLive.setTextFill(Color.RED); + } catch (UnknownHostException e) { + System.err.println("COULD NOT FIND YOUR IP ADDRESS!"); + e.printStackTrace(); + } + + } + + + @FXML + public void connectButtonPressed() { + // TODO: 10/07/17 wmu16 - Finish function + String ipAddress = ipTextField.getText().trim().toLowerCase(); + try { + ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, 4950); + controller.setClientToServerThread(clientToServerThread); + clientToServerThread.start(); + setContentPane("/views/LobbyView.fxml"); + } catch (Exception e){ + e.printStackTrace(); } } - public void switchToRaceView() { - StreamParser.boatLocations.clear(); - switchedToRaceView = true; - setContentPane("/views/RaceView.fxml"); - } - - private void updateTeamList() { - ObservableList data = FXCollections.observableArrayList(); - - teamList.setItems(data); - - boatNameCol.setCellValueFactory( - new PropertyValueFactory<>("boatName") - ); - shortNameCol.setCellValueFactory( - new PropertyValueFactory<>("shortName") - ); - countryCol.setCellValueFactory( - new PropertyValueFactory<>("country") - ); - posCol.setCellValueFactory( - new PropertyValueFactory<>("position") - ); - - // check if the boat is racing - ArrayList participants = StreamParser.getXmlObject().getRaceXML() - .getParticipants(); - ArrayList participantIDs = new ArrayList<>(); - for (Participant p : participants) { - participantIDs.add(p.getsourceID()); - } - - // add boats to the start screen list - if (StreamParser.isRaceStarted()) { // if race is started, use StreamParser.getBoatsPos() - for (Yacht boat : StreamParser.getBoatsPos().values()) { - if (participantIDs.contains(boat.getSourceID())) { - data.add(boat); - } - } - } else { // else use StreamParser.getBoats() - for (Yacht boat : StreamParser.getBoats().values()) { - if (participantIDs.contains(boat.getSourceID())) { - data.add(boat); - } - } - } - teamList.refresh(); + public void setController(Controller controller) { + this.controller = controller; } } diff --git a/src/main/java/seng302/fxObjects/BoatAnnotations.java b/src/main/java/seng302/fxObjects/BoatAnnotations.java index fbba2257..c8848418 100644 --- a/src/main/java/seng302/fxObjects/BoatAnnotations.java +++ b/src/main/java/seng302/fxObjects/BoatAnnotations.java @@ -5,8 +5,8 @@ import javafx.scene.Group; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; +import seng302.client.ClientPacketParser; import seng302.models.Yacht; -import seng302.models.stream.StreamParser; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -88,7 +88,7 @@ public class BoatAnnotations extends Group{ if (boat.getTimeTillNext() != null) { DateFormat format = new SimpleDateFormat("mm:ss"); String timeToNextMark = format - .format(boat.getTimeTillNext() - StreamParser.getCurrentTimeLong()); + .format(boat.getTimeTillNext() - ClientPacketParser.getCurrentTimeLong()); estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark); } else { estTimeToNextMarkObject.setText("Next mark: -"); @@ -97,7 +97,7 @@ public class BoatAnnotations extends Group{ if (boat.getMarkRoundTime() != null) { DateFormat format = new SimpleDateFormat("mm:ss"); String elapsedTime = format - .format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundTime()); + .format(ClientPacketParser.getCurrentTimeLong() - boat.getMarkRoundTime()); legTimeObject.setText("Last mark: " + elapsedTime); }else { legTimeObject.setText("Last mark: - "); diff --git a/src/main/java/seng302/fxObjects/BoatGroup.java b/src/main/java/seng302/fxObjects/BoatGroup.java index 0848db30..acf0c0cb 100644 --- a/src/main/java/seng302/fxObjects/BoatGroup.java +++ b/src/main/java/seng302/fxObjects/BoatGroup.java @@ -9,13 +9,13 @@ import javafx.scene.paint.Color; 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.GeometryUtils; +import seng302.utilities.GeoUtility; import seng302.controllers.CanvasController; import seng302.models.mark.GateMark; import seng302.models.mark.Mark; import seng302.models.mark.SingleMark; -import seng302.models.stream.StreamParser; /** * BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 @@ -238,7 +238,7 @@ public class BoatGroup extends Group { */ public Boolean isUpwindLeg(CanvasController canvasController, Mark nextMark) { - Double windAngle = StreamParser.getWindDirection(); + Double windAngle = ClientPacketParser.getWindDirection(); GateMark thisGateMark = (GateMark) nextMark; SingleMark nextMark1 = thisGateMark.getSingleMark1(); SingleMark nextMark2 = thisGateMark.getSingleMark2(); @@ -246,11 +246,11 @@ public class BoatGroup extends Group { Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude()); Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); - Point2D windTestPoint = GeometryUtils.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d); + Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d); - Integer boatLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint); - Integer windLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint); + Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint); + Integer windLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint); /* diff --git a/src/main/java/seng302/gameServer/ClientConnectionDelegate.java b/src/main/java/seng302/gameServer/ClientConnectionDelegate.java new file mode 100644 index 00000000..fab71cd7 --- /dev/null +++ b/src/main/java/seng302/gameServer/ClientConnectionDelegate.java @@ -0,0 +1,17 @@ +package seng302.gameServer; + +import seng302.models.Player; + +public interface ClientConnectionDelegate { + /** + * A player has connected to the server + * @param serverToClientThread The player that has connected + */ + void clientConnected(ServerToClientThread serverToClientThread); + + /** + * A player has disconnected from the server + * @param player The player that has disconnected + */ + void clientDisconnected(Player player); +} diff --git a/src/main/java/seng302/gameServer/GameStages.java b/src/main/java/seng302/gameServer/GameStages.java new file mode 100644 index 00000000..ef436f64 --- /dev/null +++ b/src/main/java/seng302/gameServer/GameStages.java @@ -0,0 +1,24 @@ +package seng302.gameServer; + +/** + * An enum describing the states of the game + * Created by wmu16 on 11/07/17. + */ +public enum GameStages { + + LOBBYING(0), + PRE_RACE(1), + RACING(2), + FINISHED(3), + CANCELLED(4); + + private long code; + + GameStages(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java new file mode 100644 index 00000000..20aaaf4f --- /dev/null +++ b/src/main/java/seng302/gameServer/GameState.java @@ -0,0 +1,101 @@ +package seng302.gameServer; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import seng302.models.Player; + +import java.util.ArrayList; +import seng302.models.Yacht; +import seng302.server.messages.BoatActionType; + +/** + * A Static class to hold information about the current state of the game (model) + * Created by wmu16 on 10/07/17. + */ +public class GameState { + + private static Long previousUpdateTime; + private static Double windDirection = 0d; + private static Double windSpeed = 0d; + + private static String hostIpAddress; + private static List players; + private static Map yachts; + private static Boolean isRaceStarted; + private static GameStages currentStage; + + public GameState(String hostIpAddress) { + GameState.hostIpAddress = hostIpAddress; + players = new ArrayList<>(); + currentStage = GameStages.LOBBYING; + isRaceStarted = false; + //set this when game stage changes to prerace + previousUpdateTime = System.currentTimeMillis(); + } + + public static String getHostIpAddress() { + return hostIpAddress; + } + + public static List getPlayers() { + return players; + } + + public static void addPlayer(Player player) { + players.add(player); + } + + public static void removePlayer(Player player) { + players.remove(player); + } + + public static void addYacht(Integer sourceId, Yacht yatch) { + yachts.put(sourceId, yatch); + } + + public static Boolean getIsRaceStarted() { + return isRaceStarted; + } + + public static GameStages getCurrentStage() { + return currentStage; + } + + public static void setCurrentStage(GameStages currentStage) { + GameState.currentStage = currentStage; + } + + public static Double getWindDirection() { + return windDirection; + } + + public static Double getWindSpeed() { + return windSpeed; + } + + public static void updateBoat(Integer sourceId, BoatActionType actionType) { + switch (actionType) { + case VMG: + break; + case SAILS_IN: + break; + case SAILS_OUT: + break; + case TACK_GYBE: + break; + case UPWIND: + break; + case DOWNWIND: + break; + } + } + + public static void update() { + Long timeInterval = System.currentTimeMillis() - previousUpdateTime; + previousUpdateTime = System.currentTimeMillis(); + for (Yacht yacht : yachts.values()) { + yacht.update(timeInterval); + } + } +} diff --git a/src/main/java/seng302/gameServer/HeartbeatThread.java b/src/main/java/seng302/gameServer/HeartbeatThread.java new file mode 100644 index 00000000..837677bd --- /dev/null +++ b/src/main/java/seng302/gameServer/HeartbeatThread.java @@ -0,0 +1,81 @@ +package seng302.gameServer; + +import seng302.models.Player; +import seng302.server.messages.Heartbeat; +import seng302.server.messages.Message; + +import java.io.IOException; +import java.util.*; + +/** + * Send Heartbeat messages to connected player at a specified interval + * Will call .clientDisconnected on the delegate when a heartbeat message + * cannot be sent to a player + */ +public class HeartbeatThread extends Thread{ + private final int HEARTBEAT_PERIOD = 5000; + private ClientConnectionDelegate delegate; + private Integer seqNum; + private Stack disconnectedPlayers; + + public HeartbeatThread(ClientConnectionDelegate delegate){ + this.delegate = delegate; + seqNum = 0; + disconnectedPlayers = new Stack<>(); + } + + /** + * A player has lost connection to the server + * The player is added to a stack so that the delegate + * can be notified + * + * @param player The player that has disconnected + */ + private void playerLostConnection(Player player){ + disconnectedPlayers.push(player); + } + + /** + * Sends a heartbeat message to each connected player + * The delegate is notified if a player has disconnected + */ + private void sendHeartbeatToAllPlayers(){ + Message heartbeat = new Heartbeat(seqNum); + + for (Player player : GameState.getPlayers()){ + if (!player.getSocket().isConnected()){ + playerLostConnection(player); + } + + try { + player.getSocket().getOutputStream().write(heartbeat.getBuffer()); + } catch (IOException e) { + playerLostConnection(player); + } + } + + updateDelegate(); + seqNum++; + } + + /** + * Notifies the delegate about + * each disconnected player + */ + private void updateDelegate() { + while (!disconnectedPlayers.empty()){ + delegate.clientDisconnected(disconnectedPlayers.pop()); + } + } + + public void run(){ + Timer t = new Timer(); + + t.schedule(new TimerTask() { + @Override + public void run() { + sendHeartbeatToAllPlayers(); + } + }, 0, HEARTBEAT_PERIOD); + } +} diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java new file mode 100644 index 00000000..5149eee5 --- /dev/null +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -0,0 +1,147 @@ +package seng302.gameServer; + +import seng302.client.ClientPacketParser; +import seng302.models.Player; +import seng302.models.stream.PacketBufferDelegate; +import seng302.models.stream.packets.StreamPacket; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.concurrent.PriorityBlockingQueue; + +/** + * A class describing the overall server, which creates and collects server threads for each client + * Created by wmu16 on 13/07/17. + */ +public class MainServerThread extends Thread implements PacketBufferDelegate, ClientConnectionDelegate{ + + private static final int PORT = 4950; + private static final Integer MAX_NUM_PLAYERS = 3; + private static final int LOG_LEVEL = 1; + + private ServerSocket serverSocket = null; + private Socket socket; + private ArrayList serverToClientThreads = new ArrayList<>(); + + private PriorityBlockingQueue packetBuffer; + + + public MainServerThread() { + try { + serverSocket = new ServerSocket(PORT); + } catch (IOException e) { + System.out.println("IO error in server thread handler upon trying to make new server socket"); + } + + packetBuffer = new PriorityBlockingQueue<>(); + } + + + public void run() { + ServerListenThread serverListenThread; + HeartbeatThread heartbeatThread; + + serverListenThread = new ServerListenThread(serverSocket, this); + heartbeatThread = new HeartbeatThread(this); + + heartbeatThread.start(); + serverListenThread.start(); + + //You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app. + while (!isInterrupted()) { + try { + Thread.sleep(1000 / 60); //60 times per second we should calculate the game state + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (GameState.getCurrentStage() == GameStages.PRE_RACE) { + GameState.update(); + } + //RACING + if (GameState.getCurrentStage() == GameStages.RACING) { + GameState.update(); + } + + + //FINISHED + else if (GameState.getCurrentStage() == GameStages.FINISHED) { + + } + updateClients(); + + while (!packetBuffer.isEmpty()){ + System.out.println("WHATUPPP"); + try { + StreamPacket packet = packetBuffer.take(); + ClientPacketParser.parsePacket(packet); + } catch (InterruptedException e) { + continue; + } + } + } + + System.out.println("WHOOPSIES"); + + + // TODO: 14/07/17 wmu16 - Send out disconnect packet to clients + try { + serverSocket.close(); + return; + } catch (IOException e) { + System.out.println("IO error in server thread handler upon closing socket"); + } + } + + + public void updateClients() { + for (ServerToClientThread serverToClientThread : serverToClientThreads) { + serverToClientThread.updateClient(); + } + } + + + static void serverLog(String message, int logLevel){ + if(logLevel <= LOG_LEVEL){ + System.out.println("[SERVER] " + message); + } + } + + @Override + public boolean addToBuffer(StreamPacket streamPacket) { + System.out.println("HEY HI"); + return packetBuffer.add(streamPacket); + } + + private void initializeRace(){ + for (ServerToClientThread serverToClientThread : serverToClientThreads) { + serverToClientThread.updateClient(); + } + } + + + /** + * A client has tried to connect to the server + * @param serverToClientThread The player that connected + */ + @Override + public void clientConnected(ServerToClientThread serverToClientThread) { + serverLog("Player Connected From " + serverToClientThread.getName(), 0); + serverToClientThreads.add(serverToClientThread); + + } + + /** + * A player has left the game, remove the player from the GameState + * @param player The player that left + */ + @Override + public void clientDisconnected(Player player) { + serverLog("Player disconnected", 0); + GameState.removePlayer(player); +// sendXml(); + } + +} diff --git a/src/main/java/seng302/gameServer/ServerListenThread.java b/src/main/java/seng302/gameServer/ServerListenThread.java new file mode 100644 index 00000000..b734ab8c --- /dev/null +++ b/src/main/java/seng302/gameServer/ServerListenThread.java @@ -0,0 +1,45 @@ +package seng302.gameServer; + +import seng302.models.Player; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +/** + * A class for a thread to listen to connections + * Created by wmu16 on 11/07/17. + */ +public class ServerListenThread extends Thread{ + private ServerSocket serverSocket; + private ClientConnectionDelegate delegate; + + public ServerListenThread(ServerSocket serverSocket, ClientConnectionDelegate delegate){ + this.serverSocket = serverSocket; + this.delegate = delegate; + } + + /** + * Listens for a connection and upon finding one, creates a Player object and adds it to the universal GameState + */ + private void acceptConnection() { + try { + Socket thisClient = serverSocket.accept(); + if (thisClient != null){ + ServerToClientThread thisConnection = new ServerToClientThread(thisClient); + thisConnection.start(); + delegate.clientConnected(thisConnection); + } + } catch (IOException e) { + e.getMessage(); + } + } + + public void run(){ + while (true){ + acceptConnection(); + } + } +} diff --git a/src/main/java/seng302/gameServer/ServerPacketParser.java b/src/main/java/seng302/gameServer/ServerPacketParser.java new file mode 100644 index 00000000..155ebe04 --- /dev/null +++ b/src/main/java/seng302/gameServer/ServerPacketParser.java @@ -0,0 +1,37 @@ +package seng302.gameServer; + +import java.util.Arrays; +import seng302.models.stream.packets.StreamPacket; +import seng302.server.messages.BoatActionType; + + +public class ServerPacketParser { + + + public static BoatActionType extractBoatAction(StreamPacket packet) { + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long actionTypeValue = bytesToLong(Arrays.copyOfRange(payload, 0, 1)); + return BoatActionType.getType((int) actionTypeValue); + } + + /** + * takes an array of up to 7 bytes and returns a positive + * long constructed from the input bytes + * + * @return a positive long if there is less than 7 bytes -1 otherwise + */ + private static long bytesToLong(byte[] bytes) { + long partialLong = 0; + int index = 0; + for (byte b : bytes) { + if (index > 6) { + return -1; + } + partialLong = partialLong | (b & 0xFFL) << (index * 8); + index++; + } + return partialLong; + } +} + diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java new file mode 100644 index 00000000..14b98db8 --- /dev/null +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -0,0 +1,195 @@ +package seng302.gameServer; + + +import java.util.Random; +import seng302.client.ClientPacketParser; +import seng302.models.Player; +import seng302.models.Yacht; +import seng302.models.stream.packets.PacketType; +import seng302.models.stream.packets.StreamPacket; +import seng302.server.messages.ChatterMessage; +import seng302.server.messages.Heartbeat; +import seng302.server.messages.BoatActionType; +import seng302.server.messages.Message; + +import java.io.*; +import java.net.Socket; +import java.util.zip.CRC32; +import java.util.zip.Checksum; +import seng302.utilities.GeoPoint; + +/** + * A class describing a single connection to a Client for the purposes of sending and receiving on its own thread. + * All server threads created and owned by the server thread handler which can trigger client updates on its threads + * Created by wmu16 on 13/07/17. + */ +public class ServerToClientThread extends Thread { + private static final Integer MAX_ID_ATTEMPTS = 10; + + private InputStream is; + private OutputStream os; + private Socket socket; + + private ByteArrayOutputStream crcBuffer; + + private Boolean userIdentified = false; + private Boolean connected = true; + private Boolean updateClient = true; + + private Integer sourceId; + + public ServerToClientThread(Socket socket) { + this.socket = socket; + try { + is = socket.getInputStream(); + os = socket.getOutputStream(); + } catch (IOException e) { + System.out.println("IO error in server thread upon grabbing streams"); + } +// threeWayHandshake(); + GameState.addPlayer(new Player(socket)); + Random rand = new Random(); + sourceId = rand.nextInt(100000); + GameState.addYacht(sourceId, new Yacht("Kappa", "Kap", new GeoPoint(0.0, 0.0), 0.0)); + } + + public void run() { + int sync1; + int sync2; + // TODO: 14/07/17 wmu16 - Work out how to fix this while loop + while(true) { + //System.out.print("."); + + 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 + ChatterMessage chatterMessage = new ChatterMessage(4, 14, "Hello, it's me"); + sendMessage(chatterMessage); + +// try { +// GameState.outputState(os); +// } catch (IOException e) { +// System.out.println("IO error in server thread upon writing to output stream"); +// } + updateClient = false; + } + + + crcBuffer = new ByteArrayOutputStream(); + sync1 = readByte(); + sync2 = readByte(); + + //checking if it is the start of the packet + if(sync1 == 0x47 && sync2 == 0x83) { + int type = readByte(); + //No. of milliseconds since Jan 1st 1970 + long timeStamp = Message.bytesToLong(getBytes(6)); + skipBytes(4); + long payloadLength = Message.bytesToLong(getBytes(2)); + byte[] payload = getBytes((int) payloadLength); + Checksum checksum = new CRC32(); + checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size()); + long computedCrc = checksum.getValue(); + long packetCrc = Message.bytesToLong(getBytes(4)); + if (computedCrc == packetCrc) { + //System.out.println("RECEIVED A PACKET"); + switch (PacketType.assignPacketType(type)) { + case BOAT_ACTION: + BoatActionType actionType = ServerPacketParser + .extractBoatAction( + new StreamPacket(type, payloadLength, timeStamp, payload)); + GameState.updateBoat(sourceId, actionType); + break; + } + } else { + System.err.println("Packet has been dropped"); + } + } + } catch (Exception e) { + e.printStackTrace(); + closeSocket(); + return; + } + } + + } + + public void updateClient() { + updateClient = true; + } + + + /** + * Tries to confirm the connection just accepted. + * Sends ID, expects that ID echoed for confirmation, + * if so, sends a confirmation packet back to that connection + * Creates a player instance with that ID and this thread and adds it to the GameState + * If not, close the socket and end the threads execution + */ + private void threeWayHandshake() { +// // TODO: 13/07/17 Finish using AC35 +// Integer playerID = GameState.getUniquePlayerID(); +// Integer confirmationID = null; +// Integer identificationAttempt = 0 +// while (!userIdentified) { +// os.write(playerID); //Send out new ID looking for echo +// confirmationID = is.read(); +// if (playerID == idConfirmation) { //ID is echoed back. Connection is a client +// os.write( some determined confirmation message ); //Confirm to client +// GameState.addPlayer(new Player(playerID, this)); //Create a player in game state for client +// userIdentified = true; +// } else if (identificationAttempt > MAX_ID_ATTEMPTS) { //No response. not a client. tidy up and go home. +// closeSocket(); +// return; +// } +// identificationAttempt++; +// } + } + + public void closeSocket() { + try { + socket.close(); + } catch (IOException e) { + System.out.println("IO error in server thread upon trying to close socket"); + } + } + + + private int readByte() throws Exception { + int currentByte = -1; + try { + // @TODO @FIX ConnectionReset Exception when a client disconnects before it is garbage collected + currentByte = is.read(); + crcBuffer.write(currentByte); + } catch (IOException e) { + e.printStackTrace(); + } + if (currentByte == -1){ + throw new Exception(); + } + return currentByte; + } + + private byte[] getBytes(int n) throws Exception{ + byte[] bytes = new byte[n]; + for (int i = 0; i < n; i++){ + bytes[i] = (byte) readByte(); + } + return bytes; + } + + private void skipBytes(long n) throws Exception{ + for (int i=0; i < n; i++){ + readByte(); + } + } + + public void sendMessage(Message message){ + try { + os.write(message.getBuffer()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/models/Player.java b/src/main/java/seng302/models/Player.java new file mode 100644 index 00000000..5ea32a4e --- /dev/null +++ b/src/main/java/seng302/models/Player.java @@ -0,0 +1,71 @@ +package seng302.models; + +import javafx.scene.paint.Color; + +import java.io.IOException; +import java.net.Socket; +import java.nio.channels.SocketChannel; + +/** + * A Class defining a player and their respective details in the game as held by the model + * Created by wmu16 on 10/07/17. + */ +public class Player { + + private Socket socket; + private Yacht yacht; + private Integer lastMarkPassed; + + + public Player(Socket socket) { + this.socket = socket; + } + + public Socket getSocket() { + return socket; + } + + public Integer getLastMarkPassed() { + return lastMarkPassed; + } + + public void setLastMarkPassed(Integer lastMarkPassed) { + this.lastMarkPassed = lastMarkPassed; + } + + public Yacht getYacht() { + return yacht; + } + + @Override + public String toString() { + String playerAddress = null; + + if (socket == null){ + return "Disconnected Player"; + } + + playerAddress = socket.getRemoteSocketAddress().toString(); + + + return playerAddress; + } + + @Override + public boolean equals(Object obj) { + if (obj == null){ + return false; + } + + if (!(obj instanceof Player)){ + return false; + } + + return ((Player) obj).socket.equals(socket); + } + + @Override + public int hashCode(){ + return socket.hashCode(); + } +} diff --git a/src/main/java/seng302/models/PolarTable.java b/src/main/java/seng302/models/PolarTable.java index 168d2291..83df7711 100644 --- a/src/main/java/seng302/models/PolarTable.java +++ b/src/main/java/seng302/models/PolarTable.java @@ -1,6 +1,9 @@ package seng302.models; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; @@ -24,9 +27,8 @@ public final class PolarTable { * Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed. * These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap * as a value - * @param file containing the polar csv information */ - public static void parsePolarFile(String file) { + public static void parsePolarFile(InputStream polarFile) { polarTable = new HashMap<>(); upwindOptimal = new HashMap<>(); downwindOptimal = new HashMap<>(); @@ -34,7 +36,7 @@ public final class PolarTable { String line; Boolean isHeaderLine = true; - try (BufferedReader br = new BufferedReader(new FileReader(file))) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) { while ((line = br.readLine()) != null) { String[] thisLine = line.split(","); @@ -69,6 +71,8 @@ public final class PolarTable { } catch (IOException e) { e.printStackTrace(); } + + } @@ -139,7 +143,7 @@ public final class PolarTable { } - private static Double getClosestMatch(Double thisWindSpeed) { + public static Double getClosestMatch(Double thisWindSpeed) { ArrayList windValues = new ArrayList<>(polarTable.keySet()); diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/models/Yacht.java index c11c7407..7e45549d 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/models/Yacht.java @@ -1,11 +1,14 @@ package seng302.models; -import javafx.scene.paint.Color; -import seng302.models.mark.Mark; -import seng302.controllers.RaceViewController; +import static seng302.utilities.GeoUtility.getGeoCoordinate; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Map; +import javafx.scene.paint.Color; +import seng302.controllers.RaceViewController; +import seng302.models.mark.Mark; +import seng302.utilities.GeoPoint; /** * Yacht class for the racing boat. @@ -24,6 +27,10 @@ public class Yacht { private String shortName; private String boatName; private String country; + + // Situational data + + // Boat status private Integer boatStatus; private Integer legNumber; @@ -31,7 +38,9 @@ public class Yacht { private Integer penaltiesServed; private Long estimateTimeAtFinish; private String position; - private double velocity; + private GeoPoint location; + private Double heading; + private Double velocity; private Long timeTillNext; private Long markRoundTime; @@ -45,8 +54,12 @@ public class Yacht { * * @param boatName Create a yacht object with name. */ - public Yacht(String boatName) { + public Yacht(String boatName, String shortName, GeoPoint location, Double heading) { this.boatName = boatName; + this.shortName = shortName; + this.location = location; + this.heading = heading; + this.velocity = 0.0; } /** @@ -74,6 +87,41 @@ public class Yacht { this.position = "-"; } + /** + * @param timeInterval since last update in milliseconds + */ + public void update(Long timeInterval) { + Double secondsElapsed = timeInterval / 1000000.0; + Double metersCovered = velocity * secondsElapsed; + location = getGeoCoordinate(location, heading, metersCovered); + } + + /** + * Adjusts the yachts velocity based on the wind direction and speed from the polar table. + * + * @param windDir current wind Direction TODO: 20/07/17 ajm412: (TWA or AWA, not 100% sure?) + * @param windSpd current wind Speed + */ + public void updateYachtVelocity(Double windDir, Double windSpd) { + Double closestSpd = PolarTable.getClosestMatch(windSpd); + Map polarsFromClosestSpd = PolarTable.getPolarTable().get(closestSpd); + + Double closest = 0d; + Double closest_key = 0d; + + for (Double key : polarsFromClosestSpd.keySet()) { + Double difference = Math.abs(key - windDir); + if (difference <= closest) { + closest = difference; + closest_key = key; + } + } +// System.out.println("Closest angle " + closest_key); +// System.out.println("WindDir " + windDir); + velocity = polarsFromClosestSpd.get(closest_key); + } + + public String getBoatType() { return boatType; } @@ -191,17 +239,17 @@ public class Yacht { this.lastMarkRounded = lastMarkRounded; } + public void setNextMark(Mark nextMark) { + this.nextMark = nextMark; + } + + public Mark getNextMark(){ + return nextMark; + } + @Override public String toString() { return boatName; } - public void setNextMark(Mark nextMark) { - this.nextMark = nextMark; - } - - public Mark getNextMark(){ - return nextMark; - } - } diff --git a/src/main/java/seng302/models/stream/PacketBufferDelegate.java b/src/main/java/seng302/models/stream/PacketBufferDelegate.java new file mode 100644 index 00000000..847b0de5 --- /dev/null +++ b/src/main/java/seng302/models/stream/PacketBufferDelegate.java @@ -0,0 +1,7 @@ +package seng302.models.stream; + +import seng302.models.stream.packets.StreamPacket; + +public interface PacketBufferDelegate { + boolean addToBuffer(StreamPacket streamPacket); +} diff --git a/src/main/java/seng302/models/stream/StreamReceiver.java b/src/main/java/seng302/models/stream/StreamReceiver.java index b1ddb996..8763a1e4 100644 --- a/src/main/java/seng302/models/stream/StreamReceiver.java +++ b/src/main/java/seng302/models/stream/StreamReceiver.java @@ -1,11 +1,15 @@ package seng302.models.stream; import seng302.models.stream.packets.StreamPacket; +import seng302.server.messages.BoatActionMessage; +import seng302.server.messages.BoatActionType; +import seng302.server.messages.Heartbeat; +import seng302.server.messages.Message; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; import java.util.Comparator; import java.util.concurrent.PriorityBlockingQueue; import java.util.zip.CRC32; @@ -13,9 +17,10 @@ import java.util.zip.Checksum; public class StreamReceiver extends Thread { - private InputStream stream; + private InputStream inputStream; + private OutputStream outputStream; private Socket host; - private ByteArrayOutputStream crcBuffer; + private ByteArrayOutputStream crcBuffer; private Thread t; private String threadName; public static PriorityBlockingQueue packetBuffer; @@ -58,49 +63,43 @@ public class StreamReceiver extends Thread { public void connect(){ - try { - stream = host.getInputStream(); - } catch (IOException e) { - e.printStackTrace(); - System.exit(1); - } - int sync1; - int sync2; - moreBytes = true; - while(moreBytes) { - try { - crcBuffer = new ByteArrayOutputStream(); - sync1 = readByte(); - sync2 = readByte(); - //checking if it is the start of the packet - if(sync1 == 0x47 && sync2 == 0x83) { - int type = readByte(); - //No. of milliseconds since Jan 1st 1970 - long timeStamp = bytesToLong(getBytes(6)); - skipBytes(4); - long payloadLength = bytesToLong(getBytes(2)); - byte[] payload = getBytes((int) payloadLength); - Checksum checksum = new CRC32(); - checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size()); - long computedCrc = checksum.getValue(); - long packetCrc = bytesToLong(getBytes(4)); - if (computedCrc == packetCrc) { - packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload)); - } else { - System.err.println("Packet has been dropped"); - } - } - } catch (Exception e) { - moreBytes = false; - } - } +// int sync1; +// int sync2; +// moreBytes = true; +// while(moreBytes) { +// try { +// crcBuffer = new ByteArrayOutputStream(); +// sync1 = readByte(); +// sync2 = readByte(); +// //checking if it is the start of the packet +// if(sync1 == 0x47 && sync2 == 0x83) { +// int type = readByte(); +// //No. of milliseconds since Jan 1st 1970 +// long timeStamp = bytesToLong(getBytes(6)); +// skipBytes(4); +// long payloadLength = bytesToLong(getBytes(2)); +// byte[] payload = getBytes((int) payloadLength); +// Checksum checksum = new CRC32(); +// checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size()); +// long computedCrc = checksum.getValue(); +// long packetCrc = bytesToLong(getBytes(4)); +// if (computedCrc == packetCrc) { +// packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload)); +// } else { +// System.err.println("Packet has been dropped"); +// } +// } +// } catch (Exception e) { +// moreBytes = false; +// } +// } } private int readByte() throws Exception { int currentByte = -1; try { - currentByte = stream.read(); + currentByte = inputStream.read(); crcBuffer.write(currentByte); } catch (IOException e) { e.printStackTrace(); diff --git a/src/main/java/seng302/models/stream/packets/PacketType.java b/src/main/java/seng302/models/stream/packets/PacketType.java index 0fd0be84..6737d53f 100644 --- a/src/main/java/seng302/models/stream/packets/PacketType.java +++ b/src/main/java/seng302/models/stream/packets/PacketType.java @@ -16,6 +16,7 @@ public enum PacketType { MARK_ROUNDING, COURSE_WIND, AVG_WIND, + BOAT_ACTION, OTHER; public static PacketType assignPacketType(int packetType){ @@ -44,6 +45,8 @@ public enum PacketType { return COURSE_WIND; case 47: return AVG_WIND; + case 100: + return BOAT_ACTION; default: } return OTHER; diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java deleted file mode 100644 index 98038ea2..00000000 --- a/src/main/java/seng302/server/ServerThread.java +++ /dev/null @@ -1,332 +0,0 @@ -package seng302.server; - -import seng302.server.messages.*; -import seng302.server.simulator.Boat; -import seng302.server.simulator.Simulator; - -import java.io.IOException; -import java.io.InputStream; -import java.util.*; - -public class ServerThread implements Runnable, Observer { - private StreamingServerSocket server; - private long startTime; - private boolean raceStarted = false; - private Map boatsFinished = new HashMap<>(); - private List boats; - private Simulator raceSimulator; - private boolean sendingRaceFinishedLocationMessages = true; - - private final int HEARTBEAT_PERIOD = 5000; - private final int RACE_STATUS_PERIOD = 1000/2; - private final int RACE_START_STATUS_PERIOD = 1000; - private final int BOAT_LOCATION_PERIOD = 1000/5; - private final int PORT_NUMBER = 4949; - private final int TIME_TILL_RACE_START = 20*1000; - private static final int LOG_LEVEL = 1; - - public ServerThread(String threadName){ - Thread runner = new Thread(this, threadName); - runner.setDaemon(true); - - raceSimulator = new Simulator(BOAT_LOCATION_PERIOD); - raceSimulator.addObserver(this); - // run race simulator, so it can send boats' static location. - Thread raceSimulatorThread = new Thread(raceSimulator, "Race Simulator"); - - boats = raceSimulator.getBoats(); - - for (Boat b : boats){ - boatsFinished.put(b.getSourceID(), false); - } - - runner.start(); - raceSimulatorThread.start(); - } - - static void serverLog(String message, int logLevel){ - if(logLevel <= LOG_LEVEL){ - System.out.println("[SERVER] " + message); - } - } - - /** - * Creates and returns an XML Message from the file specified - * @param fileName The source XML file - * @param type The XML Message type - * @return The XML Message - */ - private Message getXmlMessage(String fileName, XMLMessageSubType type){ - String fileContents = null; - - try { - InputStream thisStream = this.getClass().getResourceAsStream(fileName); - fileContents = new String(org.apache.commons.io.IOUtils.toByteArray(thisStream)); - } catch (IOException e) { - e.printStackTrace(); - } catch (NullPointerException e){ - return null; - } - - if (fileContents != null){ - return new XMLMessage(fileContents, type, server.getSequenceNumber()); - } - - return null; - } - - /** - * @return Get a race status message for the current race - */ - private Message getRaceStatusMessage(){ - List boatSubMessages = new ArrayList<>(); - BoatStatus boatStatus; - RaceStatus raceStatus; - boolean thereAreBoatsNotFinished = false; - - for (Boat b : boats){ - if (!raceStarted){ - boatStatus = BoatStatus.PRESTART; - thereAreBoatsNotFinished = true; - } - else if(boatsFinished.get(b.getSourceID())){ - boatStatus = BoatStatus.FINISHED; - } - else{ - boatStatus = BoatStatus.PRESTART; - thereAreBoatsNotFinished = true; - } - - BoatSubMessage m = new BoatSubMessage(b.getSourceID(), boatStatus, b.getLastPassedCorner().getSeqID(), 0, 0, b.getEstimatedTimeTillFinish(), b.getEstimatedTimeTillFinish()); - boatSubMessages.add(m); - } - - if (thereAreBoatsNotFinished){ - if (raceStarted){ - raceStatus = RaceStatus.STARTED; - } - else{ - long currentTime = System.currentTimeMillis(); - long timeDifference = startTime - currentTime; - - if (timeDifference > 60*3){ - raceStatus = RaceStatus.PRESTART; - } - else if (timeDifference > 60){ - raceStatus = RaceStatus.WARNING; - } - else{ - raceStatus = RaceStatus.PREPARATORY; - } - } - } - else{ - raceStatus = RaceStatus.TERMINATED; - } - - return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.SOUTH, - 100, boats.size(), RaceType.MATCH_RACE, 1, boatSubMessages); - } - - /** - * Starts an instance of the race simulator - */ - private void startRaceSim(){ - // set race started to true, so the simulator will start moving boats - raceSimulator.setRaceStarted(true); - } - - /** - * Starts sending heartbeat messages to the client - */ - private void startSendingHeartbeats() { - Timer t = new Timer(); - - t.schedule(new TimerTask() { - @Override - public void run() { - Message heartbeat = new Heartbeat(server.getSequenceNumber()); - - try { - server.send(heartbeat); - } catch (IOException e) { - e.printStackTrace(); - } - } - }, 0, HEARTBEAT_PERIOD); - } - - /** - * Start sending race start status messages until race starts - */ - private void startSendingRaceStartStatusMessages(){ - Timer t = new Timer(); - t.schedule(new TimerTask() { - @Override - public void run() { - Message raceStartStatusMessage = new RaceStartStatusMessage(server.getSequenceNumber(), startTime , 1, - RaceStartNotificationType.SET_RACE_START_TIME); - try { - if (startTime < System.currentTimeMillis() && !raceStarted){ - startRaceSim(); - raceStarted = true; - } - else{ - server.send(raceStartStatusMessage); - } - - } catch (IOException e) { - e.printStackTrace(); - } - } - }, 0, RACE_START_STATUS_PERIOD); - } - - /** - * Start sending race start status messages until race starts - */ - private void startSendingRaceStatusMessages(){ - Timer t = new Timer(); - t.schedule(new TimerTask() { - @Override - public void run() { - Message raceStatusMessage = getRaceStatusMessage(); - try { - server.send(raceStatusMessage); - } catch (IOException e) { - e.printStackTrace(); - } - } - }, 0, RACE_STATUS_PERIOD); - } - - /** - * Sends the race, boat, and regatta XML files to the client - */ - private void sendXml(){ - try{ - Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE); - Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT); - Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA); - - if (raceData != null){ - server.send(raceData); - } - if (boatData != null){ - server.send(boatData); - } - if (regatta != null){ - server.send(regatta); - } - } catch (IOException e) { - serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); - } - } - - /** - * Send the post-start race course information - */ - private void sendPostStartCourseXml(){ - Timer t = new Timer(); - t.schedule(new TimerTask() { - @Override - public void run() { - try { - Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE); - if (raceData != null) { - server.send(raceData); - } - }catch (IOException e) { - serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); - } - } - },25000); - //Delays the new course xml data for 25 seconds so the boats are able to pass the starting line - } - - public void run() { - try{ - server = new StreamingServerSocket(PORT_NUMBER); - } - catch (IOException e){ - serverLog("Failed to bind socket: " + e.getMessage(), 0); - } - - // Wait for client to connect - server.start(); - - startTime = System.currentTimeMillis() + TIME_TILL_RACE_START; - - startSendingHeartbeats(); - sendXml(); - startSendingRaceStartStatusMessages(); - startSendingRaceStatusMessages(); - sendPostStartCourseXml(); - } - - /** - * Start sending static boat position updates when race has finished - */ - private void startSendingRaceFinishedBoatPositions(){ - Timer t = new Timer(); - t.schedule(new TimerTask() { - @Override - public void run() { - try { - for (Boat b : raceSimulator.getBoats()){ - Message m = new BoatLocationMessage(b.getSourceID(), server.getSequenceNumber(), b.getLat(), - b.getLng(), b.getLastPassedCorner().getBearingToNextCorner(), - ((long) 0)); - - server.send(m); - } - - } catch (IOException e) { - e.printStackTrace(); - } - } - }, 0, BOAT_LOCATION_PERIOD); - } - - /** - * Send a boat location message when they are updated by the simulator - * @param o . - * @param arg . - */ - @Override - @SuppressWarnings("unchecked") - public void update(Observable o, Object arg) { - // Only send if server started - // TODO: I don't understand why i need to check server is null or not ... confused - haoming 2/5/17 - if(server == null || !server.isStarted()){ - return; - } - - int numOfBoatsFinished = 0; - for (Boat boat : (List) arg){ - try { - if (boat.isFinished()) { - numOfBoatsFinished ++; - if (!boatsFinished.get(boat.getSourceID())) { - boatsFinished.put(boat.getSourceID(), true); - } - } - Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(), - boat.getLng(), boat.getLastPassedCorner().getBearingToNextCorner(), - ((long) boat.getSpeed())); - server.send(m); - } catch (IOException e) { - serverLog("Couldn't send a boat status message", 3); - return; - } - catch (NullPointerException e){ - e.printStackTrace(); - } - } - - if (numOfBoatsFinished == ((List) arg).size()) { - startSendingRaceFinishedBoatPositions(); - } - - } -} diff --git a/src/main/java/seng302/server/StreamingServerSocket.java b/src/main/java/seng302/server/StreamingServerSocket.java deleted file mode 100644 index 35297f9f..00000000 --- a/src/main/java/seng302/server/StreamingServerSocket.java +++ /dev/null @@ -1,61 +0,0 @@ -package seng302.server; - -import seng302.server.messages.Message; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.nio.channels.Channels; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; -import java.util.ArrayList; -import java.util.List; - -class StreamingServerSocket { - private ServerSocketChannel socket; - private SocketChannel client; - private short seqNum; - private boolean isServerStarted; - - StreamingServerSocket(int port) throws IOException{ - socket = ServerSocketChannel.open(); - socket.socket().bind(new InetSocketAddress("localhost", port)); - //socket.setSoTimeout(10000); - seqNum = 0; - isServerStarted = false; - } - - void start(){ - try { - client = socket.accept(); - } catch (IOException e) { - e.getMessage(); - } - if (client.socket() == null){ - start(); - } - else{ - isServerStarted = true; - } - } - - void send(Message message) throws IOException{ - if (client == null){ - return; - } - - message.send(client); - - seqNum++; - } - - public short getSequenceNumber(){ - return seqNum; - } - - public boolean isStarted(){ - return isServerStarted; - } -} diff --git a/src/main/java/seng302/server/messages/BoatActionMessage.java b/src/main/java/seng302/server/messages/BoatActionMessage.java new file mode 100644 index 00000000..cf4ea918 --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatActionMessage.java @@ -0,0 +1,33 @@ +package seng302.server.messages; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * Created by kre39 on 12/07/17. + */ +public class BoatActionMessage extends Message{ + private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION; + private final int MESSAGE_SIZE = 1; + private BoatActionType actionType; + + public BoatActionMessage(BoatActionType actionType) { + this.actionType = actionType; + setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id + allocateBuffer(); + writeHeaderToBuffer(); + // Write message fields + putInt(actionType.getValue(), 1); + writeCRC(); + rewind(); + + } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + + +} diff --git a/src/main/java/seng302/server/messages/BoatActionType.java b/src/main/java/seng302/server/messages/BoatActionType.java new file mode 100644 index 00000000..f8318af7 --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatActionType.java @@ -0,0 +1,38 @@ +package seng302.server.messages; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by kre39 on 12/07/17. + */ +public enum BoatActionType { + + VMG(1), + SAILS_IN(2), + SAILS_OUT(3), + TACK_GYBE(4), + UPWIND(5), + DOWNWIND(6); + + private final int type; + private static final Map intToTypeMap = new HashMap<>(); + + static { + for (BoatActionType type : BoatActionType.values()) { + intToTypeMap.put(type.getValue(), type); + } + } + + BoatActionType(int type){ + this.type = type; + } + + public static BoatActionType getType(int value) { + return intToTypeMap.get(value); + } + + public int getValue() { + return this.type; + } +} diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java index 5e605170..b4b273a4 100644 --- a/src/main/java/seng302/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -1,7 +1,7 @@ package seng302.server.messages; import java.io.IOException; -import java.nio.channels.SocketChannel; +import java.io.OutputStream; public class BoatLocationMessage extends Message { private final int MESSAGE_SIZE = 56; @@ -64,6 +64,36 @@ public class BoatLocationMessage extends Message { this.rudderAngle = 0; setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize())); + allocateBuffer(); + writeHeaderToBuffer(); + + long headingToSend = (long)((heading/360.0) * 65535.0); + + putByte((byte) messageVersionNumber); + putInt(time, 6); + putInt((int) sourceId, 4); + putUnsignedInt((int) sequenceNum, 4); + putByte((byte) deviceType.getCode()); + putInt((int) latLonToBinaryPackedLong(latitude), 4); + putInt((int) latLonToBinaryPackedLong(longitude), 4); + putInt((int) altitude, 4); + putInt(headingToSend, 2); + putInt((int) pitch, 2); + putInt((int) roll, 2); + putInt((int) boatSpeed, 2); + putUnsignedInt((int) COG, 2); + putUnsignedInt((int) SOG, 2); + putUnsignedInt((int) apparentWindSpeed, 2); + putInt((int) apparentWindAngle, 2); + putUnsignedInt((int) trueWindSpeed, 2); + putUnsignedInt((int) trueWindDirection, 2); + putInt((int) trueWindAngle, 2); + putUnsignedInt((int) currentDrift, 2); + putUnsignedInt((int) currentSet, 2); + putInt((int) rudderAngle, 2); + + writeCRC(); + rewind(); } /** @@ -124,41 +154,4 @@ public class BoatLocationMessage extends Message { public int getSize() { return MESSAGE_SIZE; } - - - @Override - public void send(SocketChannel outputStream) throws IOException{ - allocateBuffer(); - writeHeaderToBuffer(); - - long headingToSend = (long)((heading/360.0) * 65535.0); - - putByte((byte) messageVersionNumber); - putInt(time, 6); - putInt((int) sourceId, 4); - putUnsignedInt((int) sequenceNum, 4); - putByte((byte) deviceType.getCode()); - putInt((int) latLonToBinaryPackedLong(latitude), 4); - putInt((int) latLonToBinaryPackedLong(longitude), 4); - putInt((int) altitude, 4); - putInt(headingToSend, 2); - putInt((int) pitch, 2); - putInt((int) roll, 2); - putInt((int) boatSpeed, 2); - putUnsignedInt((int) COG, 2); - putUnsignedInt((int) SOG, 2); - putUnsignedInt((int) apparentWindSpeed, 2); - putInt((int) apparentWindAngle, 2); - putUnsignedInt((int) trueWindSpeed, 2); - putUnsignedInt((int) trueWindDirection, 2); - putInt((int) trueWindAngle, 2); - putUnsignedInt((int) currentDrift, 2); - putUnsignedInt((int) currentSet, 2); - putInt((int) rudderAngle, 2); - - writeCRC(); - rewind(); - - outputStream.write(getBuffer()); - } } diff --git a/src/main/java/seng302/server/messages/ChatterMessage.java b/src/main/java/seng302/server/messages/ChatterMessage.java new file mode 100644 index 00000000..8480a9d5 --- /dev/null +++ b/src/main/java/seng302/server/messages/ChatterMessage.java @@ -0,0 +1,38 @@ +package seng302.server.messages; + +/** + * Created by kre39 on 20/07/17. + */ +public class ChatterMessage extends Message { + + private final long MESSAGE_VERSION_NUMBER = 1; + private final int MESSAGE_SIZE = 3; + private int message_type; + private int message_size = 21; + private String message; + + public ChatterMessage(int message_type, int message_size, String message) { + this.message_type = message_type; + this.message_size = message_size; + this.message = message; + + setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize())); + allocateBuffer(); + writeHeaderToBuffer(); + + putByte((byte) MESSAGE_VERSION_NUMBER); + putInt(message_type, 1); + putInt(message_size, 1); + putBytes(message.getBytes()); + + writeCRC(); + rewind(); + } + + @Override + public int getSize() { + return MESSAGE_SIZE + message_size; + } + + +} diff --git a/src/main/java/seng302/server/messages/Header.java b/src/main/java/seng302/server/messages/Header.java index c4dc6251..2b520611 100644 --- a/src/main/java/seng302/server/messages/Header.java +++ b/src/main/java/seng302/server/messages/Header.java @@ -43,10 +43,21 @@ public class Header { buff.position(buffPos); } + /** + * Reset the buffer + */ + public void reset(){ + buffPos = 0; + buff.clear(); + buff.position(buffPos); + } + /** * @return a ByteBuffer containing the message header */ public ByteBuffer getByteBuffer(){ + reset(); + putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1); putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2); diff --git a/src/main/java/seng302/server/messages/Heartbeat.java b/src/main/java/seng302/server/messages/Heartbeat.java index 8e619107..c86baac3 100644 --- a/src/main/java/seng302/server/messages/Heartbeat.java +++ b/src/main/java/seng302/server/messages/Heartbeat.java @@ -1,32 +1,16 @@ package seng302.server.messages; -import java.io.DataOutputStream; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; -import java.util.zip.CRC32; +import java.io.OutputStream; public class Heartbeat extends Message { private final int MESSAGE_SIZE = 4; - private int seqNo; /** * Heartbeat from the AC35 Streaming data spec * @param seqNo Increment every time a message is sent */ public Heartbeat(int seqNo){ - this.seqNo = seqNo; - } - - @Override - public int getSize() { - return MESSAGE_SIZE; - } - - @Override - public void send(SocketChannel outputStream) throws IOException { setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize())); allocateBuffer(); @@ -36,7 +20,11 @@ public class Heartbeat extends Message { writeCRC(); rewind(); - - outputStream.write(getBuffer()); } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + } \ No newline at end of file diff --git a/src/main/java/seng302/server/messages/MarkRoundingMessage.java b/src/main/java/seng302/server/messages/MarkRoundingMessage.java index 750efb22..5a085255 100644 --- a/src/main/java/seng302/server/messages/MarkRoundingMessage.java +++ b/src/main/java/seng302/server/messages/MarkRoundingMessage.java @@ -1,10 +1,7 @@ package seng302.server.messages; -import java.io.DataOutputStream; import java.io.IOException; -import java.nio.channels.Channels; -import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; +import java.io.OutputStream; public class MarkRoundingMessage extends Message{ private final long MESSAGE_VERSION_NUMBER = 1; @@ -33,15 +30,6 @@ public class MarkRoundingMessage extends Message{ this.markId = markId; setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize())); - } - - @Override - public int getSize() { - return MESSAGE_SIZE; - } - - @Override - public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -56,7 +44,10 @@ public class MarkRoundingMessage extends Message{ writeCRC(); rewind(); + } - outputStream.write(getBuffer()); + @Override + public int getSize() { + return MESSAGE_SIZE; } } diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java index e1bf2b53..398628ab 100644 --- a/src/main/java/seng302/server/messages/Message.java +++ b/src/main/java/seng302/server/messages/Message.java @@ -1,9 +1,9 @@ package seng302.server.messages; import java.io.IOException; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.channels.SocketChannel; import java.util.Arrays; import java.util.zip.CRC32; @@ -33,11 +33,6 @@ public abstract class Message { */ public abstract int getSize(); - /** - * Send the message as through the outputStream - */ - public abstract void send(SocketChannel outputStream) throws IOException; - /** * Allocate byte buffer to correct size */ @@ -45,6 +40,7 @@ public abstract class Message { buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE); buffer.order(ByteOrder.LITTLE_ENDIAN); bufferPosition = 0; + buffer.position(bufferPosition); } /** @@ -161,10 +157,10 @@ public abstract class Message { } /** - * @return The current buffer + * @return The current buffer as a byte array */ - public ByteBuffer getBuffer(){ - return buffer; + public byte[] getBuffer(){ + return buffer.array(); } /** @@ -193,6 +189,25 @@ public abstract class Message { return data; } + /** + * takes an array of up to 7 bytes in little endian format and + * returns a positive long constructed from the input bytes + * + * @return a positive long if there is less than 8 bytes -1 otherwise + */ + public static long bytesToLong(byte[] bytes){ + long partialLong = 0; + int index = 0; + for (byte b: bytes){ + if (index > 6){ + return -1; + } + partialLong = partialLong | (b & 0xFFL) << (index * 8); + index++; + } + return partialLong; + } + /** * Reverse an array of bytes * @param data The byte[] to reverse diff --git a/src/main/java/seng302/server/messages/MessageType.java b/src/main/java/seng302/server/messages/MessageType.java index be856dac..fccf8d45 100644 --- a/src/main/java/seng302/server/messages/MessageType.java +++ b/src/main/java/seng302/server/messages/MessageType.java @@ -16,7 +16,8 @@ public enum MessageType { BOAT_LOCATION(37), MARK_ROUNDING(38), COURSE_WIND(44), - AVERAGE_WIND(47); + AVERAGE_WIND(47), + BOAT_ACTION(100); private int code; diff --git a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java index 368a18fd..24158d62 100644 --- a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java @@ -1,10 +1,7 @@ package seng302.server.messages; -import java.io.DataOutputStream; import java.io.IOException; -import java.nio.channels.Channels; -import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; +import java.io.OutputStream; public class RaceStartStatusMessage extends Message { private final int MESSAGE_SIZE = 20; @@ -32,15 +29,6 @@ public class RaceStartStatusMessage extends Message { this.raceId = raceId; setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize())); - } - - @Override - public int getSize() { - return MESSAGE_SIZE; - } - - @Override - public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -53,7 +41,11 @@ public class RaceStartStatusMessage extends Message { writeCRC(); rewind(); - - outputStream.write(getBuffer()); } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + } diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java index 32ea9abd..2375ba17 100644 --- a/src/main/java/seng302/server/messages/RaceStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -1,7 +1,7 @@ package seng302.server.messages; import java.io.IOException; -import java.nio.channels.SocketChannel; +import java.io.OutputStream; import java.util.List; import java.util.zip.CRC32; @@ -47,22 +47,6 @@ public class RaceStatusMessage extends Message{ crc = new CRC32(); setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize())); - } - - /** - * @return the size of this message in bytes - */ - @Override - public int getSize() { - return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace)); - } - - /** - * Send this message as a stream of bytes - * @param outputStream The output stream to send the message - */ - @Override - public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -82,7 +66,14 @@ public class RaceStatusMessage extends Message{ writeCRC(); rewind(); - - outputStream.write(getBuffer()); } + + /** + * @return the size of this message in bytes + */ + @Override + public int getSize() { + return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace)); + } + } diff --git a/src/main/java/seng302/server/messages/XMLMessage.java b/src/main/java/seng302/server/messages/XMLMessage.java index 2cf3a5b5..57d10a00 100644 --- a/src/main/java/seng302/server/messages/XMLMessage.java +++ b/src/main/java/seng302/server/messages/XMLMessage.java @@ -1,12 +1,7 @@ package seng302.server.messages; -import java.io.DataOutputStream; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; -import java.util.zip.CRC32; +import java.io.OutputStream; public class XMLMessage extends Message{ private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE; @@ -35,20 +30,6 @@ public class XMLMessage extends Message{ sequence = sequenceNum; setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize())); - } - - /** - * @return The length of this message - */ - public int getSize(){ - return MESSAGE_SIZE + content.length(); - } - - /** - * Send this message as a stream of bytes - * @param outputStream The output stream to send the message - */ - public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -63,7 +44,12 @@ public class XMLMessage extends Message{ writeCRC(); rewind(); + } - outputStream.write(getBuffer()); + /** + * @return The length of this message + */ + public int getSize(){ + return MESSAGE_SIZE + content.length(); } } diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index e7a6350a..0e17ff1a 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -1,5 +1,7 @@ package seng302.utilities; +import javafx.geometry.Point2D; + public class GeoUtility { private static double EARTH_RADIUS = 6378.137; @@ -77,4 +79,55 @@ public class GeoUtility { return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng)); } + + /** + * Performs the line function on two points of a line and a test point to test which side of the line that point is + * on. If the return value is + * return 1, then the point is on one side of the line, + * return -1 then the point is on the other side of the line + * return 0 then the point is exactly on the line. + * @param linePoint1 One point of the line + * @param linePoint2 Second point of the line + * @param testPoint The point to test with this line + * @return A return value indicating which side of the line the point is on + */ + public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) { + + Double x = testPoint.getX(); + Double y = testPoint.getY(); + Double x1 = linePoint1.getX(); + Double y1 = linePoint1.getY(); + Double x2 = linePoint2.getX(); + Double y2 = linePoint2.getY(); + + Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function + + if (result > 0) { + return 1; + } + else if (result < 0) { + return -1; + } + else { + return 0; + } + } + + + /** + * Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin + * point + * @param originPoint The point with which to use as the base for our vector addition + * @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!) + * @param vectorLength The length out on this angle from the origin point to create the new point + * @return a Point2D + */ + public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) { + + Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); + Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg)); + + return new Point2D(endPointX, endPointY); + + } } diff --git a/src/main/java/seng302/utilities/GeometryUtils.java b/src/main/java/seng302/utilities/GeometryUtils.java deleted file mode 100644 index 436f767a..00000000 --- a/src/main/java/seng302/utilities/GeometryUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -package seng302.utilities; - -import javafx.geometry.Point2D; - -/** - * A Class for performing geometric calculations on the canvas - * Created by wmu16 on 24/05/17. - */ -public final class GeometryUtils { - - - /** - * Performs the line function on two points of a line and a test point to test which side of the line that point is - * on. If the return value is - * return 1, then the point is on one side of the line, - * return -1 then the point is on the other side of the line - * return 0 then the point is exactly on the line. - * @param linePoint1 One point of the line - * @param linePoint2 Second point of the line - * @param testPoint The point to test with this line - * @return A return value indicating which side of the line the point is on - */ - public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) { - - Double x = testPoint.getX(); - Double y = testPoint.getY(); - Double x1 = linePoint1.getX(); - Double y1 = linePoint1.getY(); - Double x2 = linePoint2.getX(); - Double y2 = linePoint2.getY(); - - Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function - - if (result > 0) { - return 1; - } - else if (result < 0) { - return -1; - } - else { - return 0; - } - } - - - /** - * Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin - * point - * @param originPoint The point with which to use as the base for our vector addition - * @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!) - * @param vectorLength The length out on this angle from the origin point to create the new point - * @return a Point2D - */ - public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) { - - Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); - Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg)); - - return new Point2D(endPointX, endPointY); - - } - -} diff --git a/src/main/resources/PP.png b/src/main/resources/PP.png new file mode 100644 index 00000000..1cc88a95 Binary files /dev/null and b/src/main/resources/PP.png differ diff --git a/src/main/resources/css/master.css b/src/main/resources/css/master.css index 42fc44c7..7620d831 100644 --- a/src/main/resources/css/master.css +++ b/src/main/resources/css/master.css @@ -168,7 +168,7 @@ Remove scroll bars .ui-table *.scroll-bar:vertical *.increment-arrow, .ui-table *.scroll-bar:vertical *.decrement-arrow { - -fx-background-color: null; + -fx-background-color: #0e6d6c; -fx-background-radius: 0; -fx-background-insets: 0; -fx-padding: 0; @@ -177,7 +177,7 @@ Remove scroll bars .ui-table *.scroll-bar:vertical *.increment-button, .ui-table *.scroll-bar:vertical *.decrement-button { - -fx-background-color: null; + -fx-background-color: #0e6d6c; -fx-background-radius: 0; -fx-background-insets: 0; -fx-padding: 0; diff --git a/src/main/resources/views/LobbyView.fxml b/src/main/resources/views/LobbyView.fxml new file mode 100644 index 00000000..4e413c61 --- /dev/null +++ b/src/main/resources/views/LobbyView.fxml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +