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..aa0286f4 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -4,23 +4,24 @@ 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.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(); @@ -29,51 +30,11 @@ public class App extends Application { 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/client/ClientToServerThread.java b/src/main/java/seng302/client/ClientToServerThread.java new file mode 100644 index 00000000..6a7b995d --- /dev/null +++ b/src/main/java/seng302/client/ClientToServerThread.java @@ -0,0 +1,143 @@ +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.StreamParser; +import seng302.models.stream.packets.StreamPacket; +import seng302.server.messages.BoatActionMessage; +import seng302.server.messages.BoatActionType; +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) { + StreamParser.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/Controller.java b/src/main/java/seng302/controllers/Controller.java index 73b3766b..24d433c9 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 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.models.stream.StreamParser; +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"); + StartScreenController startScreenController = (StartScreenController) setContentPane("/views/StartScreenView.fxml"); + startScreenController.setController(this); StreamParser.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/GameViewController.java b/src/main/java/seng302/controllers/GameViewController.java index 390255c9..daab7581 100644 --- a/src/main/java/seng302/controllers/GameViewController.java +++ b/src/main/java/seng302/controllers/GameViewController.java @@ -14,6 +14,7 @@ import javafx.geometry.Point2D; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.image.ImageView; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; @@ -525,4 +526,5 @@ public class GameViewController { List getMarkGroups() { return markGroups; } + } \ No newline at end of file diff --git a/src/main/java/seng302/controllers/LobbyController.java b/src/main/java/seng302/controllers/LobbyController.java new file mode 100644 index 00000000..4f2b9584 --- /dev/null +++ b/src/main/java/seng302/controllers/LobbyController.java @@ -0,0 +1,64 @@ +package seng302.controllers; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import javafx.scene.text.Text; +import seng302.gameServer.GameServerThread; +import seng302.gameServer.GameStages; +import seng302.gameServer.GameState; + +import java.io.IOException; + +/** + * A class describing the actions of the lobby screen + * Created by wmu16 on 10/07/17. + */ +public class LobbyController { + + @FXML + private GridPane lobbyScreen; + @FXML + private Text lobbyIpText; + + private GameServerThread gameServerThread; + + 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(); + } + } + + + @FXML + public void leaveLobbyButtonPressed() { + // TODO: 10/07/17 wmu16 - Finish function! + setContentPane("/views/StartScreenView.fxml"); + System.out.println("Leaving lobby!"); + GameState.setCurrentStage(GameStages.CANCELLED); + gameServerThread.terminateGame(); + } + + + @FXML + public void readyButtonPressed() { + GameState.setCurrentStage(GameStages.RACING); + } + + protected void setGameServerThread(GameServerThread gameServerThread) { + this.gameServerThread = gameServerThread; + } +} diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index f9d2f360..17e34c65 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -4,6 +4,7 @@ import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.geometry.Point2D; @@ -16,6 +17,7 @@ import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Slider; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; @@ -27,7 +29,7 @@ import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.util.Duration; import javafx.util.StringConverter; -import seng302.utilities.GeometryUtils; +import seng302.utilities.GeoUtility; import seng302.controllers.annotations.Annotation; import seng302.controllers.annotations.ImportantAnnotationController; import seng302.controllers.annotations.ImportantAnnotationDelegate; @@ -103,6 +105,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel initialiseBoatSelectionComboBox(); gameViewController.timer.start(); selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); + } @@ -136,7 +139,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel Scene scene = new Scene(fxmlLoader.load(), 469, 298); scene.getStylesheets().add(getClass().getResource("/css/master.css").toString()); stage.initStyle(StageStyle.UNDECORATED); - stage.setScene(scene); stage.show(); @@ -446,7 +448,7 @@ 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) { @@ -639,4 +641,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..738377a4 100644 --- a/src/main/java/seng302/controllers/StartScreenController.java +++ b/src/main/java/seng302/controllers/StartScreenController.java @@ -1,191 +1,92 @@ 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.gameServerWithThreading.MainServerThread; +import seng302.models.stream.StreamReceiver; -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(); +// 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(); } + } - public void switchToRaceView() { - StreamParser.boatLocations.clear(); - switchedToRaceView = true; - setContentPane("/views/RaceView.fxml"); + + @FXML + public void connectButtonPressed() { + // TODO: 10/07/17 wmu16 - Finish function + String ipAddress = ipTextField.getText().trim().toLowerCase(); + ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, 4950); + controller.setClientToServerThread(clientToServerThread); + clientToServerThread.start(); + } - 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/BoatGroup.java b/src/main/java/seng302/fxObjects/BoatGroup.java index a63b482a..5bc051db 100644 --- a/src/main/java/seng302/fxObjects/BoatGroup.java +++ b/src/main/java/seng302/fxObjects/BoatGroup.java @@ -10,8 +10,8 @@ import javafx.scene.shape.Line; import javafx.scene.shape.Polygon; import javafx.scene.transform.Rotate; import seng302.models.Yacht; -import seng302.utilities.GeometryUtils; -import seng302.controllers.GameViewController; +import seng302.utilities.GeoUtility; +import seng302.controllers.CanvasController; import seng302.models.mark.GateMark; import seng302.models.mark.Mark; import seng302.models.mark.SingleMark; @@ -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..35d839bc --- /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 player The player that has connected + */ + void clientConnected(Player player); + + /** + * 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/server/ServerThread.java b/src/main/java/seng302/gameServer/GameServerThread.java similarity index 57% rename from src/main/java/seng302/server/ServerThread.java rename to src/main/java/seng302/gameServer/GameServerThread.java index 98038ea2..9f127773 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/gameServer/GameServerThread.java @@ -1,47 +1,43 @@ -package seng302.server; +package seng302.gameServer; +import seng302.models.Player; +import seng302.models.Yacht; import seng302.server.messages.*; import seng302.server.simulator.Boat; import seng302.server.simulator.Simulator; import java.io.IOException; import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.SocketOption; +import java.net.SocketOptions; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; 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; +public class GameServerThread implements Runnable, Observer, ClientConnectionDelegate{ + + private static final Integer MAX_NUM_PLAYERS = 10; + public static final int PORT_NUMBER = 4950; - private final int HEARTBEAT_PERIOD = 5000; + private Boolean hosting = true; + + private ServerSocketChannel server; + private long startTime; + private short seqNum; + 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){ + public GameServerThread(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); - } + seqNum = 0; runner.start(); - raceSimulatorThread.start(); } static void serverLog(String message, int logLevel){ @@ -69,7 +65,7 @@ public class ServerThread implements Runnable, Observer { } if (fileContents != null){ - return new XMLMessage(fileContents, type, server.getSequenceNumber()); + return new XMLMessage(fileContents, type, seqNum); } return null; @@ -79,30 +75,33 @@ public class ServerThread implements Runnable, Observer { * @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){ + for (Player player : GameState.getPlayers()){ + Yacht y = player.getYacht(); + + if (GameState.getCurrentStage() == GameStages.PRE_RACE){ boatStatus = BoatStatus.PRESTART; thereAreBoatsNotFinished = true; } - else if(boatsFinished.get(b.getSourceID())){ - boatStatus = BoatStatus.FINISHED; + else if(false){ //@TODO if boat has finished + 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()); + BoatSubMessage m = new BoatSubMessage(y.getSourceID(), boatStatus, y.getLastMarkRounded().getId(), 0, 0, 1234l, 1234l); boatSubMessages.add(m); } if (thereAreBoatsNotFinished){ - if (raceStarted){ + if (GameState.getCurrentStage() == GameStages.RACING){ raceStatus = RaceStatus.STARTED; } else{ @@ -125,35 +124,7 @@ public class ServerThread implements Runnable, Observer { } 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); + 100, GameState.getPlayers().size(), RaceType.MATCH_RACE, 1, boatSubMessages); } /** @@ -164,15 +135,13 @@ public class ServerThread implements Runnable, Observer { t.schedule(new TimerTask() { @Override public void run() { - Message raceStartStatusMessage = new RaceStartStatusMessage(server.getSequenceNumber(), startTime , 1, + Message raceStartStatusMessage = new RaceStartStatusMessage(seqNum, startTime , 1, RaceStartNotificationType.SET_RACE_START_TIME); try { - if (startTime < System.currentTimeMillis() && !raceStarted){ - startRaceSim(); - raceStarted = true; + if (startTime < System.currentTimeMillis() && GameState.getCurrentStage() != GameStages.RACING){ } else{ - server.send(raceStartStatusMessage); + broadcast(raceStartStatusMessage); } } catch (IOException e) { @@ -186,13 +155,14 @@ public class ServerThread implements Runnable, Observer { * 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); + broadcast(raceStatusMessage); } catch (IOException e) { e.printStackTrace(); } @@ -210,13 +180,13 @@ public class ServerThread implements Runnable, Observer { Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA); if (raceData != null){ - server.send(raceData); + broadcast(raceData); } if (boatData != null){ - server.send(boatData); + broadcast(boatData); } if (regatta != null){ - server.send(regatta); + broadcast(regatta); } } catch (IOException e) { serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); @@ -234,58 +204,115 @@ public class ServerThread implements Runnable, Observer { try { Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE); if (raceData != null) { - server.send(raceData); + broadcast(raceData); } }catch (IOException e) { serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); } } - },25000); + },1000); //Delays the new course xml data for 25 seconds so the boats are able to pass the starting line } public void run() { + ServerListenThread serverListenThread; + HeartbeatThread heartbeatThread; + Boolean serverIsSendingMessages = false; + try{ - server = new StreamingServerSocket(PORT_NUMBER); + server = ServerSocketChannel.open(); + server.socket().bind(new InetSocketAddress("localhost", PORT_NUMBER)); + + serverListenThread = new ServerListenThread(server, this); + heartbeatThread = new HeartbeatThread(this); + + heartbeatThread.start(); + serverListenThread.start(); } catch (IOException e){ serverLog("Failed to bind socket: " + e.getMessage(), 0); } - // Wait for client to connect - server.start(); + while (hosting) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } - startTime = System.currentTimeMillis() + TIME_TILL_RACE_START; + if (GameState.getCurrentStage() == GameStages.RACING && !serverIsSendingMessages) { + serverLog("Race Started", 0); - startSendingHeartbeats(); - sendXml(); - startSendingRaceStartStatusMessages(); - startSendingRaceStatusMessages(); - sendPostStartCourseXml(); + sendXml(); + startSendingRaceStartStatusMessages(); + //startSendingRaceStatusMessages(); + sendPostStartCourseXml(); + serverIsSendingMessages = true; + } + + else if (GameState.getCurrentStage() == GameStages.FINISHED) { + serverLog("Race Finished", 0); + } + + startTime = System.currentTimeMillis() + TIME_TILL_RACE_START; + } + } + +// /** +// * 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(), seqNum, b.getLat(), +// b.getLng(), b.getLastPassedCorner().getBearingToNextCorner(), +// ((long) 0)); +// +// server.send(m); +// } +// +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +// }, 0, BOAT_LOCATION_PERIOD); +// } + + /** + * A client has tried to connect to the server + * @param player The player that connected + */ + @Override + public void clientConnected(Player player) { + if (GameState.getPlayers().size() < MAX_NUM_PLAYERS && GameState.getCurrentStage() == GameStages.LOBBYING) { + serverLog("Player Connected", 0); + GameState.addPlayer(player); + sendXml(); + } } /** - * Start sending static boat position updates when race has finished + * A player has left the game, remove the player from the GameState + * @param player The player that left */ - 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)); + @Override + public void clientDisconnected(Player player) { + serverLog("Player disconnected", 0); + GameState.removePlayer(player); + sendXml(); + } - server.send(m); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - }, 0, BOAT_LOCATION_PERIOD); + void broadcast(Message message) throws IOException{ + for(Player player : GameState.getPlayers()) { + //heh + player.getSocketChannel().socket().getOutputStream().write(message.getBuffer()); + } + seqNum++; } /** @@ -296,7 +323,7 @@ public class ServerThread implements Runnable, Observer { @Override @SuppressWarnings("unchecked") public void update(Observable o, Object arg) { - // Only send if server started + /* 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; @@ -314,19 +341,28 @@ public class ServerThread implements Runnable, Observer { Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(), boat.getLng(), boat.getLastPassedCorner().getBearingToNextCorner(), ((long) boat.getSpeed())); - server.send(m); + broadcast(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(); - } +// if (numOfBoatsFinished == ((List) arg).size()) { +// startSendingRaceFinishedBoatPositions(); +// } + //} + + public void terminateGame() { + try { + //TODO: for now, I just close the socket, but i think we should terminate the whole thread instead. -hyi25 13 July + server.close(); + } catch (IOException e) { + e.printStackTrace(); + } } } 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..d36968fc --- /dev/null +++ b/src/main/java/seng302/gameServer/GameState.java @@ -0,0 +1,65 @@ +package seng302.gameServer; + +import seng302.models.Player; + +import java.util.ArrayList; + +/** + * 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 String hostIpAddress; + private static ArrayList players; + private static Boolean isRaceStarted; + private static GameStages currentStage; + + public GameState(String hostIpAddress) { + GameState.hostIpAddress = hostIpAddress; + players = new ArrayList<>(); + currentStage = GameStages.LOBBYING; + isRaceStarted = false; + } + + public static String getHostIpAddress() { + return hostIpAddress; + } + + public static ArrayList getPlayers() { + return players; + } + + public static void addPlayer(Player player) { + players.add(player); + } + + public static void removePlayer(Player player) { + players.remove(player); + } + + public static Boolean getIsRaceStarted() { + return isRaceStarted; + } + + public static GameStages getCurrentStage() { + return currentStage; + } + + public static void setCurrentStage(GameStages currentStage) { + GameState.currentStage = currentStage; + } + + /** + * This iterates through all players and updates each players info to its new state based on its current data + */ + private void update(){ + for(Player player : players) { + // TODO: 10/07/17 wmu16 - Update all player info + } + } + + + + +} diff --git a/src/main/java/seng302/gameServer/HeartbeatThread.java b/src/main/java/seng302/gameServer/HeartbeatThread.java new file mode 100644 index 00000000..b5ac550e --- /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; + + 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.getSocketChannel().isConnected()){ + playerLostConnection(player); + } +// +// try { +// player.getSocketChannel().socket().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/ServerListenThread.java b/src/main/java/seng302/gameServer/ServerListenThread.java new file mode 100644 index 00000000..cfcbdcaa --- /dev/null +++ b/src/main/java/seng302/gameServer/ServerListenThread.java @@ -0,0 +1,42 @@ +package seng302.gameServer; + +import seng302.models.Player; + +import java.io.IOException; +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 ServerSocketChannel socketChannel; + private ClientConnectionDelegate delegate; + + ServerListenThread(ServerSocketChannel socketChannel, ClientConnectionDelegate delegate){ + this.socketChannel = socketChannel; + 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 { + SocketChannel thisClient = socketChannel.accept(); + if (thisClient.socket() != null){ + Player thisPlayer = new Player(thisClient); + delegate.clientConnected(thisPlayer); + } + } catch (IOException e) { + e.getMessage(); + } + } + + public void run(){ + while (true){ + acceptConnection(); + } + } +} diff --git a/src/main/java/seng302/gameServerWithThreading/MainServerThread.java b/src/main/java/seng302/gameServerWithThreading/MainServerThread.java new file mode 100644 index 00000000..226e1dc8 --- /dev/null +++ b/src/main/java/seng302/gameServerWithThreading/MainServerThread.java @@ -0,0 +1,111 @@ +package seng302.gameServerWithThreading; + +import seng302.gameServer.GameStages; +import seng302.gameServer.GameState; +import seng302.models.stream.PacketBufferDelegate; +import seng302.models.stream.StreamParser; +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{ + + private static final int PORT = 4950; + private static final Integer MAX_NUM_PLAYERS = 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() { + //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(); + } + + //LOBBYING + if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState.getPlayers().size() < MAX_NUM_PLAYERS) { + try { + // TODO: 14/07/17 wmu16 - Get out of blocking call somehow after a time + socket = serverSocket.accept(); + } catch (IOException e) { + System.out.println("IO error in server thread handler upon trying to accept connection"); + } + ServerToClientThread thread = new ServerToClientThread(socket, this); + serverToClientThreads.add(thread); + thread.start(); + } + + //RACING + else if (GameState.getCurrentStage() == GameStages.RACING) { + + } + + + //FINISHED + else if (GameState.getCurrentStage() == GameStages.FINISHED) { + + } + + updateClients(); + + while (!packetBuffer.isEmpty()){ + System.out.println("WHATUPPP"); + try { + StreamPacket packet = packetBuffer.take(); + StreamParser.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(); + } + } + + @Override + public boolean addToBuffer(StreamPacket streamPacket) { + System.out.println("HEY HI"); + return packetBuffer.add(streamPacket); + } +} diff --git a/src/main/java/seng302/gameServerWithThreading/ServerToClientThread.java b/src/main/java/seng302/gameServerWithThreading/ServerToClientThread.java new file mode 100644 index 00000000..957cf043 --- /dev/null +++ b/src/main/java/seng302/gameServerWithThreading/ServerToClientThread.java @@ -0,0 +1,166 @@ +package seng302.gameServerWithThreading; + +import seng302.gameServer.GameState; +import seng302.models.Player; +import seng302.models.stream.PacketBufferDelegate; +import seng302.models.stream.StreamParser; +import seng302.models.stream.packets.StreamPacket; +import seng302.server.messages.Message; + +import java.io.*; +import java.net.Socket; +import java.util.zip.CRC32; +import java.util.zip.Checksum; + +/** + * 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 final PacketBufferDelegate packetBufferDelegate; + + private Boolean userIdentified = false; + private Boolean connected = true; + private Boolean updateClient = true; + + public ServerToClientThread(Socket socket, PacketBufferDelegate packetBufferDelegate) { + this.socket = socket; + try { + is = socket.getInputStream(); + os = socket.getOutputStream(); + } catch (IOException e) { + System.out.println("IO error in server thread upon grabbing streams"); + } + this.packetBufferDelegate = packetBufferDelegate; + // threeWayHandshake(); + GameState.addPlayer(new Player(socket.getChannel())); + } + + 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) { + StreamParser.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; + } + } + + } + + 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 { + 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/models/Player.java b/src/main/java/seng302/models/Player.java new file mode 100644 index 00000000..fb062d0d --- /dev/null +++ b/src/main/java/seng302/models/Player.java @@ -0,0 +1,73 @@ +package seng302.models; + +import javafx.scene.paint.Color; + +import java.io.IOException; +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 SocketChannel socketChannel; + private Yacht yacht; + private Integer lastMarkPassed; + + + public Player(SocketChannel socketChannel) { + this.socketChannel = socketChannel; + } + + public SocketChannel getSocketChannel() { + return socketChannel; + } + + 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 (socketChannel == null){ + return "Disconnected Player"; + } + + try { + playerAddress = socketChannel.getRemoteAddress().toString(); + } catch (IOException e) { + e.printStackTrace(); + } + + return playerAddress; + } + + @Override + public boolean equals(Object obj) { + if (obj == null){ + return false; + } + + if (!(obj instanceof Player)){ + return false; + } + + return ((Player) obj).socketChannel.equals(socketChannel); + } + + @Override + public int hashCode(){ + return socketChannel.hashCode(); + } +} diff --git a/src/main/java/seng302/models/PolarTable.java b/src/main/java/seng302/models/PolarTable.java index 168d2291..b40b54dd 100644 --- a/src/main/java/seng302/models/PolarTable.java +++ b/src/main/java/seng302/models/PolarTable.java @@ -24,9 +24,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 +33,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 +68,8 @@ public final class PolarTable { } catch (IOException e) { e.printStackTrace(); } + + } diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/models/Yacht.java index c11c7407..7ea7e97e 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/models/Yacht.java @@ -24,6 +24,10 @@ public class Yacht { private String shortName; private String boatName; private String country; + + // Situational data + + // Boat status private Integer boatStatus; private Integer legNumber; @@ -31,6 +35,9 @@ public class Yacht { private Integer penaltiesServed; private Long estimateTimeAtFinish; private String position; + private Double lat; + private Double lon; + private Float heading; private double velocity; private Long timeTillNext; private Long markRoundTime; @@ -191,17 +198,41 @@ public class Yacht { this.lastMarkRounded = lastMarkRounded; } + public void setNextMark(Mark nextMark) { + this.nextMark = nextMark; + } + + public Mark getNextMark(){ + return nextMark; + } + + public Double getLat() { + return lat; + } + + public void setLat(Double lat) { + this.lat = lat; + } + + public Double getLon() { + return lon; + } + + public void setLon(Double lon) { + this.lon = lon; + } + + public Float getHeading() { + return heading; + } + + public void setHeading(Float heading) { + this.heading = heading; + } + @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/StreamParser.java b/src/main/java/seng302/models/stream/StreamParser.java index e7286561..f1ea501a 100644 --- a/src/main/java/seng302/models/stream/StreamParser.java +++ b/src/main/java/seng302/models/stream/StreamParser.java @@ -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; @@ -32,7 +31,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 StreamParser{ public static ConcurrentHashMap> markLocations = new ConcurrentHashMap<>(); public static ConcurrentHashMap> boatLocations = new ConcurrentHashMap<>(); @@ -58,54 +57,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 StreamParser() { } - - /** - * 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,7 +106,8 @@ public class StreamParser extends Thread { case AVG_WIND: extractAvgWind(packet); break; - default: + case BOAT_ACTION: + extractBoatAction(packet); break; } } catch (NullPointerException e) { @@ -527,6 +489,27 @@ public class StreamParser extends Thread { long speed4 = bytesToLong(Arrays.copyOfRange(payload, 21, 23)); } + + private static void extractBoatAction(StreamPacket packet) { + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long actionType = bytesToLong(Arrays.copyOfRange(payload, 0, 1)); + if (actionType == 1) { + System.out.println("VMG"); + } else if (actionType == 2) { + System.out.println("SAILS IN"); + } else if (actionType == 3) { + System.out.println("SAILS OUT"); + } else if (actionType == 4) { + System.out.println("TACK/GYBE"); + } else if (actionType == 5) { + System.out.println("UPWIND"); + } else if (actionType == 6) { + System.out.println("DOWNWIND"); + } + + } + /** * takes an array of up to 7 bytes and returns a positive * long constructed from the input bytes 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/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..9dfc5769 --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatActionMessage.java @@ -0,0 +1,32 @@ +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((int) BoatActionType.getBoatPacketType(actionType), 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..e323b6fe --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatActionType.java @@ -0,0 +1,42 @@ +package seng302.server.messages; + +/** + * 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 int type; + + BoatActionType(int type){ + this.type = type; + } + + public int getType(){ + return this.type; + } + + public static Short getBoatPacketType(BoatActionType type){ + switch (type){ + case VMG: + return 1; + case SAILS_IN: + return 2; + case SAILS_OUT: + return 3; + case TACK_GYBE: + return 4; + case UPWIND: + return 5; + case DOWNWIND: + return 6; + } + return 0; + } +} 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/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..478ba962 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 @@ -204,4 +219,5 @@ public abstract class Message { data[right] = (byte) (temp & 0xff); } } + } 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..803c2df1 --- /dev/null +++ b/src/main/resources/views/LobbyView.fxml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +