diff --git a/pom.xml b/pom.xml
index 8d10c6fc..296a4f01 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,13 @@
2.7.13
test
+
+
+
+ org.freemarker
+ freemarker
+ 2.3.26-incubating
+
diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java
index e1b8b70b..ed6227bb 100644
--- a/src/main/java/seng302/App.java
+++ b/src/main/java/seng302/App.java
@@ -26,7 +26,7 @@ public class App extends Application {
primaryStage.show();
primaryStage.setOnCloseRequest(e -> {
- StreamParser.appClose();
+// ClientPacketParser.appClose();
StreamReceiver.noMoreBytes();
System.exit(0);
});
diff --git a/src/main/java/seng302/client/ClientState.java b/src/main/java/seng302/client/ClientState.java
new file mode 100644
index 00000000..64512a1b
--- /dev/null
+++ b/src/main/java/seng302/client/ClientState.java
@@ -0,0 +1,78 @@
+package seng302.client;
+
+import com.sun.org.apache.xpath.internal.operations.Bool;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import seng302.models.Yacht;
+
+/**
+ * Used by the client to store static variables to be used in game.
+ */
+public class ClientState {
+
+ private static String hostIp = "";
+ private static Boolean isHost = false;
+ private static Boolean raceStarted = false;
+ private static Boolean connectedToHost = false;
+ private static Map boats = new ConcurrentHashMap<>();
+ private static Boolean dirtyState = true;
+ private static String clientSourceId = "";
+
+ public static String getHostIp() {
+ return hostIp;
+ }
+
+ public static void setHostIp(String hostIp) {
+ ClientState.hostIp = hostIp;
+ }
+
+ public static Boolean isHost() {
+ return isHost;
+ }
+
+ public static void setHost(Boolean isHost) {
+ ClientState.isHost = isHost;
+ }
+
+ public static Boolean isRaceStarted() {
+ return raceStarted;
+ }
+
+ public static void setRaceStarted(Boolean raceStarted) {
+ ClientState.raceStarted = raceStarted;
+ }
+
+ public static Boolean isConnectedToHost() {
+ return connectedToHost;
+ }
+
+ public static void setConnectedToHost(Boolean connectedToHost) {
+ ClientState.connectedToHost = connectedToHost;
+ }
+
+ public static Map getBoats() {
+ return boats;
+ }
+
+ public static Boolean isDirtyState() {
+ return dirtyState;
+ }
+
+ public static void setDirtyState(Boolean dirtyState) {
+ ClientState.dirtyState = dirtyState;
+ }
+
+ public static String getClientSourceId() {
+ return clientSourceId;
+ }
+
+ public static void setClientSourceId(String clientSourceId) {
+ ClientState.clientSourceId = clientSourceId;
+ }
+
+ public static void setBoats(Map boats) {
+ ClientState.boats = boats;
+ }
+}
diff --git a/src/main/java/seng302/client/ClientStateQueryingRunnable.java b/src/main/java/seng302/client/ClientStateQueryingRunnable.java
new file mode 100644
index 00000000..67cf1dbf
--- /dev/null
+++ b/src/main/java/seng302/client/ClientStateQueryingRunnable.java
@@ -0,0 +1,43 @@
+package seng302.client;
+
+import java.util.Observable;
+
+/**
+ * Used by LobbyController to run a separate thread-loop
+ * updates the controller when change is detected.
+ */
+public class ClientStateQueryingRunnable extends Observable implements Runnable {
+
+ private Boolean terminate = false;
+
+ public ClientStateQueryingRunnable() {}
+
+ @Override
+ public void run() {
+ while(!terminate) {
+ // Sleeping the thread so it will respond to the if statement below
+ // if you know a better fix, pls tell me :) -ryan
+ try {
+ Thread.sleep(0);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ if (ClientState.isRaceStarted() && ClientState.isConnectedToHost()) {
+ setChanged();
+ notifyObservers("game started");
+ terminate();
+ }
+
+ if (ClientState.isDirtyState()) {
+ setChanged();
+ notifyObservers("update players");
+ ClientState.setDirtyState(false);
+ }
+ }
+ }
+
+ public void terminate() {
+ terminate = true;
+ }
+}
diff --git a/src/main/java/seng302/controllers/Controller.java b/src/main/java/seng302/controllers/Controller.java
new file mode 100644
index 00000000..550f6f81
--- /dev/null
+++ b/src/main/java/seng302/controllers/Controller.java
@@ -0,0 +1,99 @@
+package seng302.controllers;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ResourceBundle;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.fxml.Initializable;
+import javafx.scene.Parent;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.AnchorPane;
+import seng302.client.ClientPacketParser;
+import seng302.client.ClientToServerThread;
+import seng302.server.messages.BoatActionMessage;
+import seng302.server.messages.BoatActionType;
+
+public class Controller implements Initializable {
+
+ @FXML
+ private AnchorPane contentPane;
+ private ClientToServerThread clientToServerThread;
+ private long lastSendingTime;
+ private int KEY_STROKE_SENDING_FREQUENCY = 50;
+
+ private Object setContentPane(String jfxUrl) {
+ try {
+ contentPane.getChildren().removeAll();
+ contentPane.getChildren().clear();
+ contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
+ FXMLLoader fxmlLoader = new FXMLLoader((getClass().getResource(jfxUrl)));
+ Parent view = fxmlLoader.load();
+ contentPane.getChildren().addAll(view);
+ return fxmlLoader.getController();
+ } catch (javafx.fxml.LoadException e) {
+ System.err.println(e.getCause());
+ } catch (IOException e) {
+ System.err.println(e);
+ }
+ return null;
+ }
+
+ @Override
+ public void initialize(URL location, ResourceBundle resources) {
+ contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
+ StartScreenController startScreenController = (StartScreenController) setContentPane("/views/StartScreenView.fxml");
+ startScreenController.setController(this);
+ ClientPacketParser.boatLocations.clear();
+
+ lastSendingTime = System.currentTimeMillis();
+ }
+
+ /** Handle the key-pressed event from the text field. */
+ public void keyPressed(KeyEvent e) {
+ BoatActionMessage boatActionMessage;
+ long currentTime = System.currentTimeMillis();
+ if (currentTime - lastSendingTime > KEY_STROKE_SENDING_FREQUENCY) {
+ lastSendingTime = currentTime;
+ switch (e.getCode()) {
+ case SPACE: // align with vmg
+ boatActionMessage = new BoatActionMessage(BoatActionType.VMG);
+ clientToServerThread.sendBoatActionMessage(boatActionMessage);
+ break;
+ case PAGE_UP: // upwind
+ boatActionMessage = new BoatActionMessage(BoatActionType.UPWIND);
+ clientToServerThread.sendBoatActionMessage(boatActionMessage);
+ break;
+ case PAGE_DOWN: // downwind
+ boatActionMessage = new BoatActionMessage(BoatActionType.DOWNWIND);
+ clientToServerThread.sendBoatActionMessage(boatActionMessage);
+ break;
+ case ENTER: // tack/gybe
+ boatActionMessage = new BoatActionMessage(BoatActionType.TACK_GYBE);
+ clientToServerThread.sendBoatActionMessage(boatActionMessage);
+ break;
+ //TODO Allow a zoom in and zoom out methods
+ case Z: // zoom in
+ System.out.println("Zoom in");
+ break;
+ case X: // zoom out
+ System.out.println("Zoom out");
+ break;
+ }
+ }
+ }
+
+ public void keyReleased(KeyEvent e) {
+ switch (e.getCode()) {
+ //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
+ case SHIFT: // sails in/sails out
+ BoatActionMessage boatActionMessage = new BoatActionMessage(BoatActionType.SAILS_IN);
+ clientToServerThread.sendBoatActionMessage(boatActionMessage);
+ break;
+ }
+ }
+
+ public void setClientToServerThread(ClientToServerThread ctt) {
+ clientToServerThread = ctt;
+ }
+}
diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java
new file mode 100644
index 00000000..436da210
--- /dev/null
+++ b/src/main/java/seng302/controllers/RaceViewController.java
@@ -0,0 +1,656 @@
+package seng302.controllers;
+
+import javafx.animation.KeyFrame;
+import javafx.animation.Timeline;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Point2D;
+import javafx.scene.Scene;
+import javafx.scene.chart.LineChart;
+import javafx.scene.chart.NumberAxis;
+import javafx.scene.chart.XYChart;
+import javafx.scene.chart.XYChart.Series;
+import javafx.scene.control.Button;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.Slider;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Line;
+import javafx.scene.text.Text;
+import javafx.stage.Stage;
+import javafx.stage.StageStyle;
+import javafx.util.Duration;
+import javafx.util.StringConverter;
+import seng302.client.ClientPacketParser;
+import seng302.utilities.GeoUtility;
+import seng302.controllers.annotations.Annotation;
+import seng302.controllers.annotations.ImportantAnnotationController;
+import seng302.controllers.annotations.ImportantAnnotationDelegate;
+import seng302.controllers.annotations.ImportantAnnotationsState;
+import seng302.fxObjects.BoatGroup;
+import seng302.fxObjects.MarkGroup;
+import seng302.models.*;
+import seng302.models.mark.GateMark;
+import seng302.models.mark.Mark;
+import seng302.models.mark.SingleMark;
+import seng302.models.stream.XMLParser;
+
+import java.io.IOException;
+import java.util.*;
+import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
+import java.util.stream.Collectors;
+
+/**
+ * Created by ptg19 on 29/03/17.
+ */
+public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
+
+ @FXML
+ private Text windSpeedText;
+ @FXML
+ private LineChart raceSparkLine;
+ @FXML
+ private NumberAxis sparklineYAxis;
+ @FXML
+ private VBox positionVbox;
+ @FXML
+ private CheckBox toggleFps;
+ @FXML
+ private Text timerLabel;
+ @FXML
+ private AnchorPane contentAnchorPane;
+ @FXML
+ private Text windArrowText, windDirectionText;
+ @FXML
+ private Slider annotationSlider;
+ @FXML
+ private Button selectAnnotationBtn;
+ @FXML
+ private ComboBox boatSelectionComboBox;
+ @FXML
+ private CanvasController includedCanvasController;
+
+ private static ArrayList startingBoats = new ArrayList<>();
+ private boolean displayFps;
+ private Timeline timerTimeline;
+ private Stage stage;
+ private static HashMap> sparkLineData = new HashMap<>();
+ private static ArrayList racingBoats = new ArrayList<>();
+ private ImportantAnnotationsState importantAnnotations;
+ private Yacht selectedBoat;
+
+ public void initialize() {
+ // Load a default important annotation state
+ importantAnnotations = new ImportantAnnotationsState();
+
+ //Formatting the y axis of the sparkline
+ raceSparkLine.getYAxis().setRotate(180);
+ raceSparkLine.getYAxis().setTickLabelRotation(180);
+ raceSparkLine.getYAxis().setTranslateX(-5);
+ raceSparkLine.getYAxis().setAutoRanging(false);
+ sparklineYAxis.setTickMarkVisible(false);
+ startingBoats = new ArrayList<>(ClientPacketParser.getBoats().values());
+
+ includedCanvasController.setup(this);
+ includedCanvasController.initializeCanvas();
+ initializeUpdateTimer();
+ initialiseFPSCheckBox();
+ initialiseAnnotationSlider();
+ initialiseBoatSelectionComboBox();
+ includedCanvasController.timer.start();
+ selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
+
+ }
+
+
+ /**
+ * The important annotations have been changed, update this view
+ *
+ * @param importantAnnotationsState The current state of the selected annotations
+ */
+ public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
+ this.importantAnnotations = importantAnnotationsState;
+ setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
+ }
+
+
+ /**
+ * Loads the "select annotations" view in a new window
+ */
+ private void loadSelectAnnotationView() {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader();
+ Stage stage = new Stage();
+
+ // Set controller
+ ImportantAnnotationController controller = new ImportantAnnotationController(this,
+ stage);
+ fxmlLoader.setController(controller);
+
+ // Load FXML and set CSS
+ fxmlLoader
+ .setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml"));
+ Scene scene = new Scene(fxmlLoader.load(), 469, 298);
+ scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
+ stage.initStyle(StageStyle.UNDECORATED);
+ stage.setScene(scene);
+ stage.show();
+
+ controller.loadState(importantAnnotations);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ private void initialiseFPSCheckBox() {
+ displayFps = true;
+ toggleFps.selectedProperty().addListener(
+ (observable, oldValue, newValue) -> displayFps = !displayFps);
+ }
+
+ private void initialiseAnnotationSlider() {
+ annotationSlider.setLabelFormatter(new StringConverter() {
+ @Override
+ public String toString(Double n) {
+ if (n == 0) {
+ return "None";
+ }
+ if (n == 1) {
+ return "Important";
+ }
+ if (n == 2) {
+ return "All";
+ }
+
+ return "All";
+ }
+
+ @Override
+ public Double fromString(String s) {
+ switch (s) {
+ case "None":
+ return 0d;
+ case "Important":
+ return 1d;
+ case "All":
+ return 2d;
+
+ default:
+ return 2d;
+ }
+ }
+ });
+
+ annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
+ setAnnotations((int) annotationSlider.getValue()));
+
+ annotationSlider.setValue(2);
+ }
+
+
+ /**
+ * Used to add any new boats into the race that may have started late or not have had data received yet
+ */
+ void updateSparkLine(){
+ // Collect the racing boats that aren't already in the chart
+ ArrayList sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceId())
+ && yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
+
+ // Obtain the qualifying boats to set the max on the Y axis
+ racingBoats = startingBoats.stream().filter(yacht ->
+ yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
+ sparklineYAxis.setUpperBound(racingBoats.size() + 1);
+
+ // Create a new data series for new boats
+ sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> {
+ Series yachtData = new Series<>();
+ yachtData.setName(yacht.getBoatName());
+ yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
+ sparkLineData.put(yacht.getSourceId(), yachtData);
+ });
+
+ // Lambda function to sort the series in order of leg (later legs shown more to the right)
+ List> positions = new ArrayList<>(sparkLineData.values());
+ Collections.sort(positions, (o1, o2) -> {
+ Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
+ Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
+ if (leg2 < leg1){
+ return 1;
+ } else {
+ return -1;
+ }
+ });
+
+ // Adds the new data series to the sparkline (and set the colour of the series)
+ raceSparkLine.setCreateSymbols(false);
+ positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> {
+ raceSparkLine.getData().add(spark);
+ spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
+ });
+ }
+
+
+ /**
+ * Updates the yachts sparkline of the desired boat and using the new leg number
+ * @param yacht The yacht to be updated on the sparkline
+ * @param legNumber the leg number that the position will be assigned to
+ */
+ public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){
+ XYChart.Series positionData = sparkLineData.get(yacht.getSourceId());
+ positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
+ }
+
+
+ /**
+ * gets the rgb string of the boats colour to use for the chart via css
+ * @param boatName boat passed in to get the boats colour
+ * @return the colour as an rgb string
+ */
+ private String getBoatColorAsRGB(String boatName){
+ Color color = Color.WHITE;
+ for (Yacht yacht: startingBoats){
+ if (Objects.equals(yacht.getBoatName(), boatName)){
+ color = yacht.getColour();
+ }
+ }
+ if (color == null){
+ return String.format( "#%02X%02X%02X",255,255,255);
+ }
+ return String.format( "#%02X%02X%02X",
+ (int)( color.getRed() * 255 ),
+ (int)( color.getGreen() * 255 ),
+ (int)( color.getBlue() * 255 ) );
+ }
+
+
+ /**
+ * Initalises a timer which updates elements of the RaceView such as wind direction, boat
+ * orderings etc.. which are dependent on the info from the stream parser constantly.
+ * Updates of each of these attributes are called ONCE EACH SECOND
+ */
+ private void initializeUpdateTimer() {
+ timerTimeline = new Timeline();
+ timerTimeline.setCycleCount(Timeline.INDEFINITE);
+ // Run timer update every second
+ timerTimeline.getKeyFrames().add(
+ new KeyFrame(Duration.seconds(1),
+ event -> {
+ updateRaceTime();
+ updateWindDirection();
+// updateOrder();
+ updateBoatSelectionComboBox();
+ })
+ );
+
+ // Start the timer
+ timerTimeline.playFromStart();
+ }
+
+
+ /**
+ * Iterates over all corners until ones SeqID matches with the boats current leg number.
+ * Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark
+ * Returns null if no next mark found.
+ * @param bg The BoatGroup to find the next mark of
+ * @return The next Mark or null if none found
+ */
+ private Mark getNextMark(BoatGroup bg) {
+ Integer legNumber = bg.getBoat().getLegNumber();
+
+ List markSequence = ClientPacketParser.getXmlObject()
+ .getRaceXML().getCompoundMarkSequence();
+
+ if (legNumber == 0) {
+ return null;
+ } else if (legNumber == markSequence.size() - 1) {
+ return null;
+ }
+
+ for (XMLParser.RaceXMLObject.Corner corner : markSequence) {
+ if (legNumber + 2 == corner.getSeqID()) {
+ Integer thisCompoundMarkID = corner.getCompoundMarkID();
+
+ for (Mark mark : ClientPacketParser.getXmlObject().getRaceXML()
+ .getAllCompoundMarks()) {
+ if (mark.getCompoundMarkID() == thisCompoundMarkID) {
+ return mark;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Updates the wind direction arrow and text as from info from the ClientPacketParser
+ */
+ private void updateWindDirection() {
+ windDirectionText.setText(String.format("%.1f°", ClientPacketParser.getWindDirection()));
+ windArrowText.setRotate(ClientPacketParser.getWindDirection());
+ windSpeedText.setText(String.format("%.1f Knots", ClientPacketParser.getWindSpeed()));
+ }
+
+
+ /**
+ * Updates the clock for the race
+ */
+ private void updateRaceTime() {
+ if (ClientPacketParser.isRaceFinished()) {
+ timerLabel.setFill(Color.RED);
+ timerLabel.setText("Race Finished!");
+ } else {
+ timerLabel.setText(getTimeSinceStartOfRace());
+ }
+ }
+
+
+ /**
+ * Grabs the boats currently in the race as from the ClientPacketParser and sets them to be selectable
+ * in the boat selection combo box
+ */
+ private void updateBoatSelectionComboBox() {
+ ObservableList observableBoats = FXCollections
+ .observableArrayList(ClientPacketParser.getBoatsPos().values());
+ boatSelectionComboBox.setItems(observableBoats);
+ }
+
+
+ /**
+ * Updates the order of the boats as from the ClientPacketParser and sets them in the boat order
+ * section
+ */
+ private void updateOrder() {
+ positionVbox.getChildren().clear();
+ positionVbox.getChildren().removeAll();
+ positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
+
+ // list of racing boat id
+ ArrayList participants = ClientPacketParser.getXmlObject().getRaceXML()
+ .getParticipants();
+ ArrayList participantIDs = new ArrayList<>();
+ for (Participant p : participants) {
+ participantIDs.add(p.getsourceID());
+ }
+
+ 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() + ". " +
+ boat.getShortName() + " (Finished)");
+ textToAdd.setFill(Paint.valueOf("#d3d3d3"));
+ positionVbox.getChildren().add(textToAdd);
+
+ } else {
+ Text textToAdd = new Text(boat.getPosition() + ". " +
+ boat.getShortName() + " ");
+ textToAdd.setFill(Paint.valueOf("#d3d3d3"));
+ textToAdd.setStyle("");
+ positionVbox.getChildren().add(textToAdd);
+ }
+ }
+ }
+ } else {
+ for (Yacht boat : ClientPacketParser.getBoats().values()) {
+ if (participantIDs.contains(boat.getSourceId())) { // check if the boat is racing
+ Text textToAdd = new Text(boat.getPosition() + ". " +
+ boat.getShortName() + " ");
+ textToAdd.setFill(Paint.valueOf("#d3d3d3"));
+ textToAdd.setStyle("");
+ positionVbox.getChildren().add(textToAdd);
+ }
+ }
+ }
+ }
+
+
+ private void updateLaylines(BoatGroup bg) {
+
+ Mark nextMark = getNextMark(bg);
+ Boolean isUpwind = null;
+ // Can only calc leg direction if there is a next mark and it is a gate mark
+ if (nextMark != null) {
+ if (nextMark instanceof GateMark) {
+ if (bg.isUpwindLeg(includedCanvasController, nextMark)) {
+ isUpwind = true;
+ } else {
+ isUpwind = false;
+ }
+
+ for(MarkGroup mg : includedCanvasController.getMarkGroups()) {
+
+ mg.removeLaylines();
+
+ if (mg.getMainMark().getId() == nextMark.getId()) {
+
+ SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1();
+ SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2();
+ Point2D markPoint1 = includedCanvasController.findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude());
+ Point2D markPoint2 = includedCanvasController.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
+ HashMap angleAndSpeed;
+ if (isUpwind) {
+ angleAndSpeed = PolarTable
+ .getOptimalUpwindVMG(ClientPacketParser.getWindSpeed());
+ } else {
+ angleAndSpeed = PolarTable
+ .getOptimalDownwindVMG(ClientPacketParser.getWindSpeed());
+ }
+
+ Double resultingAngle = angleAndSpeed.keySet().iterator().next();
+
+
+ Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
+ Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
+ Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
+ Line rightLayline = new Line();
+ Line leftLayline = new Line();
+ if (lineFuncResult == 1) {
+ rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle,
+ ClientPacketParser
+ .getWindDirection());
+ leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle,
+ ClientPacketParser
+ .getWindDirection());
+ } else if (lineFuncResult == -1) {
+ rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle,
+ ClientPacketParser
+ .getWindDirection());
+ leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle,
+ ClientPacketParser
+ .getWindDirection());
+ }
+
+ leftLayline.setStrokeWidth(0.5);
+ leftLayline.setStroke(bg.getBoat().getColour());
+
+ rightLayline.setStrokeWidth(0.5);
+ rightLayline.setStroke(bg.getBoat().getColour());
+
+ bg.setLaylines(leftLayline, rightLayline);
+ mg.addLaylines(leftLayline, rightLayline);
+
+ }
+ }
+ }
+ }
+ }
+
+
+ private Point2D getPointRotation(Point2D ref, Double distance, Double angle){
+ Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle);
+ Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle);
+
+ return new Point2D(newX, newY);
+ }
+
+
+ public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
+
+ Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle);
+ Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
+ return line;
+
+ }
+
+
+ public Line makeRightLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
+
+ Point2D ep = getPointRotation(startPoint, 50.0, baseAngle - layLineAngle);
+ Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
+ return line;
+
+ }
+
+
+ /**
+ * Initialised the combo box with any boats currently in the race and adds the required listener
+ * for the combobox to take action upon selection
+ */
+ private void initialiseBoatSelectionComboBox() {
+ updateBoatSelectionComboBox();
+ boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
+ //This listener is fired whenever the combo box changes. This means when the values are updated
+ //We dont want to set the selected value if the values are updated but nothing clicked (null)
+ if (newValue != null && newValue != selectedBoat) {
+ Yacht thisYacht = (Yacht) newValue;
+ setSelectedBoat(thisYacht);
+ }
+ });
+ }
+
+
+ /**
+ * Display the list of boats in the order they finished the race
+ */
+ private void loadRaceResultView() {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
+
+ try {
+ contentAnchorPane.getChildren().removeAll();
+ contentAnchorPane.getChildren().clear();
+ contentAnchorPane.getChildren().addAll((Pane) loader.load());
+
+ } catch (javafx.fxml.LoadException e) {
+ System.err.println(e.getCause());
+ } catch (IOException e) {
+ System.err.println(e);
+ }
+ }
+
+
+ /**
+ * Convert seconds to a string of the format mm:ss
+ *
+ * @param time the time in seconds
+ * @return a formatted string
+ */
+ public String convertTimeToMinutesSeconds(int time) {
+ if (time < 0) {
+ return String.format("-%02d:%02d", (time * -1) / 60, (time * -1) % 60);
+ }
+ return String.format("%02d:%02d", time / 60, time % 60);
+ }
+
+ private String getTimeSinceStartOfRace() {
+ String timerString = "0:00";
+ if (ClientPacketParser.getTimeSinceStart() > 0) {
+ String timerMinute = Long.toString(ClientPacketParser.getTimeSinceStart() / 60);
+ String timerSecond = Long.toString(ClientPacketParser.getTimeSinceStart() % 60);
+ if (timerSecond.length() == 1) {
+ timerSecond = "0" + timerSecond;
+ }
+ timerString = "-" + timerMinute + ":" + timerSecond;
+ } else {
+ String timerMinute = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() / 60);
+ String timerSecond = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() % 60);
+ if (timerSecond.length() == 1) {
+ timerSecond = "0" + timerSecond;
+ }
+ timerString = timerMinute + ":" + timerSecond;
+ }
+ return timerString;
+ }
+
+
+ boolean isDisplayFps() {
+ return displayFps;
+ }
+
+ private void setAnnotations(Integer annotationLevel) {
+ switch (annotationLevel) {
+ // No Annotations
+ case 0:
+ for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
+ bg.setVisibility(false, false, false, false, false, false);
+ }
+ break;
+ // Important Annotations
+ case 1:
+ for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
+ bg.setVisibility(
+ importantAnnotations.getAnnotationState(Annotation.NAME),
+ importantAnnotations.getAnnotationState(Annotation.SPEED),
+ importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK),
+ importantAnnotations.getAnnotationState(Annotation.LEGTIME),
+ importantAnnotations.getAnnotationState(Annotation.TRACK),
+ importantAnnotations.getAnnotationState(Annotation.WAKE)
+ );
+ }
+ break;
+ // All Annotations
+ case 2:
+ for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
+ bg.setVisibility(true, true, true, true, true, true);
+ }
+ break;
+ }
+ }
+
+
+ /**
+ * Sets all the annotations of the selected boat to be visible and all others to be hidden
+ *
+ * @param yacht The yacht for which we want to view all annotations
+ */
+ private void setSelectedBoat(Yacht yacht) {
+ for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
+ //We need to iterate over all race groups to get the matching boat group belonging to this boat if we
+ //are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
+ if (bg.getBoat().getHullID().equals(yacht.getHullID())) {
+ updateLaylines(bg);
+ bg.setIsSelected(true);
+ selectedBoat = yacht;
+ } else {
+ bg.setIsSelected(false);
+ }
+ }
+ }
+
+ void setStage(Stage stage) {
+ this.stage = stage;
+ }
+
+ Stage getStage() {
+ return stage;
+ }
+
+ /**
+ * Used for when the boat attempts to add data to the sparkline (first checks if the sparkline contains info on it)
+ * @param yachtId
+ * @return
+ */
+ public static boolean sparkLineStatus(Integer yachtId) {
+ return sparkLineData.containsKey(yachtId);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java
index fcae2838..f7c95a6a 100644
--- a/src/main/java/seng302/gameServer/GameState.java
+++ b/src/main/java/seng302/gameServer/GameState.java
@@ -1,8 +1,12 @@
package seng302.gameServer;
+import java.util.*;
+
+import seng302.models.Player;
import seng302.model.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)
@@ -10,23 +14,38 @@ import java.util.ArrayList;
*/
public class GameState {
+ private static Long previousUpdateTime;
+ public static Double windDirection;
+ private static Double windSpeed;
+
private static String hostIpAddress;
- private static ArrayList players;
+ private static List players;
+ private static Map yachts;
private static Boolean isRaceStarted;
private static GameStages currentStage;
public GameState(String hostIpAddress) {
+ windDirection = 170d;
+ windSpeed = 10000d;
+ yachts = new HashMap<>();
+ players = new ArrayList<>();
+
+
GameState.hostIpAddress = hostIpAddress;
players = new ArrayList<>();
currentStage = GameStages.LOBBYING;
isRaceStarted = false;
+ yachts = new HashMap<>();
+ //set this when game stage changes to prerace
+ previousUpdateTime = System.currentTimeMillis();
+ yachts = new HashMap<>();
}
public static String getHostIpAddress() {
return hostIpAddress;
}
- public static ArrayList getPlayers() {
+ public static List getPlayers() {
return players;
}
@@ -38,6 +57,14 @@ public class GameState {
players.remove(player);
}
+ public static void addYacht(Integer sourceId, Yacht yacht) {
+ yachts.put(sourceId, yacht);
+ }
+
+ public static void removeYacht(Integer yachtId) {
+ yachts.remove(yachtId);
+ }
+
public static Boolean getIsRaceStarted() {
return isRaceStarted;
}
@@ -50,16 +77,72 @@ public class GameState {
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
+ public static Double getWindDirection() {
+ return windDirection;
+ }
+
+ public static Double getWindSpeed() {
+ return windSpeed;
+ }
+
+ public static Map getYachts() {
+ return yachts;
+ }
+
+ public static void updateBoat(Integer sourceId, BoatActionType actionType) {
+ Yacht playerYacht = yachts.get(sourceId);
+// System.out.println("-----------------------");
+ switch (actionType) {
+ case VMG:
+// System.out.println("Snapping to VMG");
+ // TODO: 22/07/17 wmu16 - Add in the vmg calculation code here
+ break;
+ case SAILS_IN:
+ playerYacht.toggleSailIn();
+// System.out.println("Toggling Sails");
+ break;
+ case SAILS_OUT:
+ playerYacht.toggleSailIn();
+// System.out.println("Toggling Sails");
+ break;
+ case TACK_GYBE:
+ playerYacht.tackGybe(windDirection);
+// System.out.println("Tack/Gybe");
+ break;
+ case UPWIND:
+ playerYacht.turnUpwind();
+// System.out.println("Moving upwind");
+ break;
+ case DOWNWIND:
+ playerYacht.turnDownwind();
+// System.out.println("Moving downwind");
+ break;
+ }
+
+// System.out.println("-----------------------");
+// System.out.println("Heading: " + playerYacht.getHeading());
+// System.out.println("Sails are in: " + playerYacht.getSailIn());
+// System.out.println("Lat: " + playerYacht.getLocation().getLat());
+// System.out.println("Lng: " + playerYacht.getLocation().getLng());
+// System.out.println("-----------------------\n");
+ }
+
+ public static void update() {
+
+ Long timeInterval = System.currentTimeMillis() - previousUpdateTime;
+ previousUpdateTime = System.currentTimeMillis();
+ for (Yacht yacht : yachts.values()) {
+ yacht.update(timeInterval);
}
}
-
-
+ /**
+ * Generates a new ID based off the size of current players + 1
+ * @return a playerID to be allocated to a new connetion
+ */
+ public static Integer getUniquePlayerID() {
+ // TODO: 22/07/17 wmu16 - This may not be robust enough and may have to be improved on.
+ return yachts.size() + 1;
+ }
}
diff --git a/src/main/java/seng302/gameServer/HeartbeatThread.java b/src/main/java/seng302/gameServer/HeartbeatThread.java
index 2057b1bd..415d1e57 100644
--- a/src/main/java/seng302/gameServer/HeartbeatThread.java
+++ b/src/main/java/seng302/gameServer/HeartbeatThread.java
@@ -13,7 +13,7 @@ import java.util.*;
* cannot be sent to a player
*/
public class HeartbeatThread extends Thread{
- private final int HEARTBEAT_PERIOD = 5000;
+ private final int HEARTBEAT_PERIOD = 200;
private ClientConnectionDelegate delegate;
private Integer seqNum;
private Stack disconnectedPlayers;
@@ -43,7 +43,7 @@ public class HeartbeatThread extends Thread{
Message heartbeat = new Heartbeat(seqNum);
for (Player player : GameState.getPlayers()){
- if (!player.getSocket().isConnected()){
+ if (!player.getSocket().isConnected()) {
playerLostConnection(player);
}
diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java
index ebcdc51c..d2cc3473 100644
--- a/src/main/java/seng302/gameServer/MainServerThread.java
+++ b/src/main/java/seng302/gameServer/MainServerThread.java
@@ -1,9 +1,11 @@
package seng302.gameServer;
-import seng302.model.Player;
-import seng302.model.stream.PacketBufferDelegate;
-import seng302.model.stream.parsers.StreamParser;
-import seng302.model.stream.packets.StreamPacket;
+import java.time.LocalDateTime;
+import java.util.Observable;
+import seng302.client.ClientPacketParser;
+import seng302.models.Player;
+import seng302.models.stream.PacketBufferDelegate;
+import seng302.models.stream.packets.StreamPacket;
import java.io.IOException;
import java.net.ServerSocket;
@@ -15,12 +17,15 @@ 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{
+public class MainServerThread extends Observable implements Runnable, PacketBufferDelegate, ClientConnectionDelegate{
- private static final int PORT = 4950;
+ private static final int PORT = 4942;
private static final Integer MAX_NUM_PLAYERS = 3;
+ private static final Integer UPDATES_PER_SECOND = 2;
private static final int LOG_LEVEL = 1;
+ private Thread thread;
+
private ServerSocket serverSocket = null;
private Socket socket;
private ArrayList serverToClientThreads = new ArrayList<>();
@@ -32,10 +37,13 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
try {
serverSocket = new ServerSocket(PORT);
} catch (IOException e) {
- System.out.println("IO error in server thread handler upon trying to make new server socket");
+ serverLog("IO error in server thread handler upon trying to make new server socket", 0);
}
packetBuffer = new PriorityBlockingQueue<>();
+
+ thread = new Thread(this);
+ thread.start();
}
@@ -49,43 +57,40 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
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()) {
+ while (!thread.isInterrupted()) {
try {
- Thread.sleep(1000 / 60); //60 times per second we should calculate the game state
+ Thread.sleep(1000 / UPDATES_PER_SECOND);
} catch (InterruptedException e) {
e.printStackTrace();
}
-
+ if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
+ GameState.update();
+ }
//RACING
if (GameState.getCurrentStage() == GameStages.RACING) {
+ GameState.update();
updateClients();
}
-
//FINISHED
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
}
- updateClients();
-
while (!packetBuffer.isEmpty()){
- System.out.println("WHATUPPP");
try {
StreamPacket packet = packetBuffer.take();
- StreamParser.parsePacket(packet);
+ 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();
@@ -95,7 +100,6 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
}
}
-
public void updateClients() {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.updateClient();
@@ -105,32 +109,26 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
static void serverLog(String message, int logLevel){
if(logLevel <= LOG_LEVEL){
- System.out.println("[SERVER] " + message);
+ System.out.println("[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + 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);
+ serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
serverToClientThreads.add(serverToClientThread);
-
+ this.addObserver(serverToClientThread);
+ setChanged();
+ notifyObservers();
}
/**
@@ -139,9 +137,26 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
*/
@Override
public void clientDisconnected(Player player) {
- serverLog("Player disconnected", 0);
+ try {
+ player.getSocket().close();
+ } catch (Exception e) {
+ serverLog("Cannot disconnect the socket for the disconnected player.", 0);
+ }
+ serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
+ GameState.removeYacht(player.getYacht().getSourceId());
GameState.removePlayer(player);
-// sendXml();
+ for (ServerToClientThread serverToClientThread : serverToClientThreads) {
+ if (serverToClientThread.getSocket() == player.getSocket()) {
+ this.deleteObserver(serverToClientThread);
+ }
+ }
+ setChanged();
+ notifyObservers();
}
+ public void startGame() {
+ for (ServerToClientThread serverToClientThread : serverToClientThreads) {
+ serverToClientThread.sendRaceStatusMessage();
+ }
+ }
}
diff --git a/src/main/java/seng302/gameServer/ServerListenThread.java b/src/main/java/seng302/gameServer/ServerListenThread.java
index 123e1c53..1313dc6c 100644
--- a/src/main/java/seng302/gameServer/ServerListenThread.java
+++ b/src/main/java/seng302/gameServer/ServerListenThread.java
@@ -25,7 +25,6 @@ public class ServerListenThread extends Thread{
Socket thisClient = serverSocket.accept();
if (thisClient != null){
ServerToClientThread thisConnection = new ServerToClientThread(thisClient);
- thisConnection.start();
delegate.clientConnected(thisConnection);
}
} catch (IOException e) {
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
index 68306595..0fd3768f 100644
--- a/src/main/java/seng302/gameServer/ServerToClientThread.java
+++ b/src/main/java/seng302/gameServer/ServerToClientThread.java
@@ -1,34 +1,74 @@
package seng302.gameServer;
-import seng302.model.Player;
-import seng302.model.stream.parsers.StreamParser;
-import seng302.model.stream.packets.StreamPacket;
-import seng302.server.messages.ChatterMessage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.SocketException;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.Random;
+import java.util.zip.CRC32;
+import java.util.zip.Checksum;
+import org.apache.commons.io.IOUtils;
+import seng302.models.Player;
+import seng302.models.Yacht;
+import seng302.models.stream.packets.PacketType;
+import seng302.models.stream.packets.StreamPacket;
+import seng302.models.xml.Race;
+import seng302.models.xml.Regatta;
+import seng302.models.xml.XMLGenerator;
+import seng302.server.messages.BoatActionType;
+import seng302.server.messages.BoatLocationMessage;
+import seng302.server.messages.BoatStatus;
+import seng302.server.messages.BoatSubMessage;
import seng302.server.messages.Message;
import java.io.*;
import java.net.Socket;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
+import seng302.server.messages.RaceStatus;
+import seng302.server.messages.RaceStatusMessage;
+import seng302.server.messages.RaceType;
+import seng302.server.messages.XMLMessage;
+import seng302.server.messages.XMLMessageSubType;
+import seng302.server.messages.XMLMessage;
+import seng302.server.messages.XMLMessageSubType;
+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.
+ * 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 {
+public class ServerToClientThread implements Runnable, Observer {
+ private static final Integer LOG_LEVEL = 1;
private static final Integer MAX_ID_ATTEMPTS = 10;
+ private Thread thread;
+
private InputStream is;
private OutputStream os;
private Socket socket;
- private ByteArrayOutputStream crcBuffer;
+ private ByteArrayOutputStream crcBuffer;
private Boolean userIdentified = false;
private Boolean connected = true;
private Boolean updateClient = true;
+// private Boolean initialisedRace = true;
+
+ private Integer seqNo;
+ private Integer sourceId;
+
+ private XMLGenerator xml;
public ServerToClientThread(Socket socket) {
this.socket = socket;
@@ -38,39 +78,69 @@ public class ServerToClientThread extends Thread {
} catch (IOException e) {
System.out.println("IO error in server thread upon grabbing streams");
}
-// threeWayHandshake();
- GameState.addPlayer(new Player(socket));
+ //Attempt threeway handshake with connection
+ sourceId = GameState.getUniquePlayerID();
+ if (threeWayHandshake(sourceId)) {
+ serverLog("Successful handshake. Client allocated id: " + sourceId, 1);
+ Yacht yacht = new Yacht("Yacht", sourceId, sourceId.toString(), "Kapa", "Kappa", "NZ");
+// Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0);
+ GameState.addYacht(sourceId, yacht);
+ GameState.addPlayer(new Player(socket, yacht));
+ } else {
+ serverLog("Unsuccessful handshake. Connection rejected", 1);
+ closeSocket();
+ return;
+ }
+
+ seqNo = 0;
+ thread = new Thread(this);
+ thread.start();
+ }
+
+ static void serverLog(String message, int logLevel) {
+ if (logLevel <= LOG_LEVEL) {
+ System.out.println(
+ "[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
+ }
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ sendSetupMessages();
}
public void run() {
int sync1;
int sync2;
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
- while(true) {
- //System.out.print(".");
+
+ while (socket.isConnected()) {
try {
+// if (initialisedRace) {
+// sendSetupMessages();
+// initialisedRace = false;
+// }
+
//Perform a write if it is time to as delegated by the MainServerThread
if (updateClient) {
// TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream
- ChatterMessage chatterMessage = new ChatterMessage(4, 14, "Hello, it's me");
- sendMessage(chatterMessage);
-
+// ChatterMessage chatterMessage = new ChatterMessage(4, 14, "Hello, it's me");
+// sendMessage(chatterMessage);
// try {
// GameState.outputState(os);
// } catch (IOException e) {
// System.out.println("IO error in server thread upon writing to output stream");
// }
+// sendBoatLocationPackets();
updateClient = false;
}
-
crcBuffer = new ByteArrayOutputStream();
sync1 = readByte();
sync2 = readByte();
-
//checking if it is the start of the packet
- if(sync1 == 0x47 && sync2 == 0x83) {
+ if (sync1 == 0x47 && sync2 == 0x83) {
int type = readByte();
//No. of milliseconds since Jan 1st 1970
long timeStamp = Message.bytesToLong(getBytes(6));
@@ -83,14 +153,22 @@ public class ServerToClientThread extends Thread {
long packetCrc = Message.bytesToLong(getBytes(4));
if (computedCrc == packetCrc) {
//System.out.println("RECEIVED A PACKET");
- 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!?!?
+ 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");
+ serverLog("Packet has been dropped", 1);
}
}
} catch (Exception e) {
- e.printStackTrace();
+ // TODO: 24/07/17 zyt10 - fix a logic here when a client disconnected
+// serverLog("ERROR OCCURRED, CLOSING SERVER CONNECTION: " + socket.getRemoteSocketAddress().toString(), 1);
+// e.printStackTrace();
closeSocket();
return;
}
@@ -98,7 +176,34 @@ public class ServerToClientThread extends Thread {
}
+ private void sendSetupMessages() {
+ xml = new XMLGenerator();
+ Race race = new Race();
+
+ for (Yacht yacht : GameState.getYachts().values()) {
+ race.addBoat(yacht);
+ }
+
+ //@TODO calculate lat/lng values
+ xml.setRegatta(new Regatta("RaceVision Test Game", 57.6679590, 11.8503233));
+ xml.setRace(race);
+
+ XMLMessage xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA,
+ xml.getRegattaAsXml().length());
+ sendMessage(xmlMessage);
+
+ xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT,
+ xml.getBoatsAsXml().length());
+ sendMessage(xmlMessage);
+
+ xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE,
+ xml.getRaceAsXml().length());
+ sendMessage(xmlMessage);
+// System.out.println("Sent xml messages for " + thread.getName());
+ }
+
public void updateClient() {
+ sendBoatLocationPackets();
updateClient = true;
}
@@ -109,28 +214,33 @@ public class ServerToClientThread extends Thread {
* if so, sends a confirmation packet back to that connection
* Creates a player instance with that ID and this thread and adds it to the GameState
* If not, close the socket and end the threads execution
+ *
+ * @param id the id to try and assign to the connection
+ * @return A boolean indicating if it was a successful handshake
*/
- private 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++;
-// }
+ private Boolean threeWayHandshake(Integer id) {
+ Integer confirmationID = null;
+ Integer identificationAttempt = 0;
+ while (!userIdentified) {
+ try {
+ os.write(id); //Send out new ID looking for echo
+ confirmationID = is.read();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (id.equals(confirmationID)) { //ID is echoed back. Connection is a client
+ return true;
+ } else if (identificationAttempt > MAX_ID_ATTEMPTS) { //No response. not a client. tidy up and go home.
+ return false;
+ }
+ identificationAttempt++;
+ }
+
+ return true;
}
- public void closeSocket() {
+ private void closeSocket() {
try {
socket.close();
} catch (IOException e) {
@@ -148,31 +258,101 @@ public class ServerToClientThread extends Thread {
} catch (IOException e) {
e.printStackTrace();
}
- if (currentByte == -1){
+ if (currentByte == -1) {
throw new Exception();
}
return currentByte;
}
- private byte[] getBytes(int n) throws Exception{
+ private byte[] getBytes(int n) throws Exception {
byte[] bytes = new byte[n];
- for (int i = 0; i < n; i++){
+ 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++){
+ private void skipBytes(long n) throws Exception {
+ for (int i = 0; i < n; i++) {
readByte();
}
}
- public void sendMessage(Message message){
+ public void sendMessage(Message message) {
try {
os.write(message.getBuffer());
+ } catch (SocketException e) {
+ //serverLog("Player " + sourceId + " side socket disconnected", 0);
+ return;
} catch (IOException e) {
e.printStackTrace();
}
}
+
+ private int getSeqNo() {
+ seqNo++;
+ return seqNo;
+ }
+
+
+ private void sendBoatLocationPackets() {
+ ArrayList yachts = new ArrayList<>(GameState.getYachts().values());
+ for (Yacht yacht : yachts) {
+// System.out.println("[SERVER] Lat: " + yacht.getLocation().getLat() + " Lon: " + yacht.getLocation().getLng());
+ BoatLocationMessage boatLocationMessage =
+ new BoatLocationMessage(
+ yacht.getSourceId(),
+ getSeqNo(),
+ yacht.getLocation().getLat(),
+ yacht.getLocation().getLng(),
+ yacht.getHeading(),
+ (long) yacht.getVelocity());
+
+ sendMessage(boatLocationMessage);
+ }
+ }
+
+ public Thread getThread() {
+ return thread;
+ }
+
+ public void sendRaceStatusMessage() {
+ // variables taken from GameServerThread
+ int TIME_TILL_RACE_START = 20 * 1000;
+ long startTime = System.currentTimeMillis() + TIME_TILL_RACE_START;
+
+ List boatSubMessages = new ArrayList<>();
+ BoatStatus boatStatus;
+ RaceStatus raceStatus;
+
+ for (Player player : GameState.getPlayers()) {
+ Yacht y = player.getYacht();
+
+ if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
+ boatStatus = BoatStatus.PRESTART;
+ } else if (GameState.getCurrentStage() == GameStages.RACING) {
+ boatStatus = BoatStatus.RACING;
+ } else {
+ boatStatus = BoatStatus.UNDEFINED;
+ }
+
+ BoatSubMessage m = new BoatSubMessage(y.getSourceId(), boatStatus, 0, 0, 0, 1234l,
+ 1234l);
+ boatSubMessages.add(m);
+ }
+
+ if (GameState.getCurrentStage() == GameStages.RACING) {
+ raceStatus = RaceStatus.STARTED;
+ } else {
+ raceStatus = RaceStatus.WARNING;
+ }
+
+ sendMessage(new RaceStatusMessage(1, raceStatus, startTime, GameState.getWindDirection(),
+ GameState.getWindSpeed().longValue(), GameState.getPlayers().size(),
+ RaceType.MATCH_RACE, 1, boatSubMessages));
+ }
+
+ public Socket getSocket() {
+ return socket;
+ }
}
diff --git a/src/main/java/seng302/model/Boat.java b/src/main/java/seng302/model/Boat.java
index caed3621..caf676b4 100644
--- a/src/main/java/seng302/model/Boat.java
+++ b/src/main/java/seng302/model/Boat.java
@@ -6,10 +6,18 @@ import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.scene.paint.Color;
import seng302.model.mark.Mark;
+import static seng302.utilities.GeoUtility.getGeoCoordinate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import javafx.scene.paint.Color;
+import seng302.client.ClientPacketParser;
+import seng302.controllers.RaceViewController;
+import seng302.gameServer.GameState;
+import seng302.models.mark.Mark;
+import seng302.utilities.GeoPoint;
+
/**
* Yacht class for the racing boat.
*
@@ -18,11 +26,17 @@ import java.text.SimpleDateFormat;
*/
public class Boat {
+ private final Double TURN_STEP = 5.0;
+
+ private Double lastHeading;
+ private Boolean sailIn;
+
+
// Used in boat group
private Color colour = Color.BLACK;
private String boatType;
- private Integer sourceID;
+ private Integer sourceId;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
private String boatName;
@@ -40,30 +54,167 @@ public class Boat {
private ReadOnlyDoubleWrapper velocity = new ReadOnlyDoubleWrapper();
private ReadOnlyLongWrapper timeTillNext = new ReadOnlyLongWrapper();
private ReadOnlyLongWrapper timeSinceLastMark = new ReadOnlyLongWrapper();
+ private String position;
+ private GeoPoint location;
+ private Double heading;
+ private Double velocity;
+ private Long timeTillNext;
+ private Long markRoundTime;
// Mark rounding
private Mark lastMarkRounded;
private Mark nextMark;
+
+ /**
+ * @param location latlon location of the boat stored in a geopoint
+ * @param heading heading of the boat in degrees from 0 to 365 with 0 being north
+ */
+ public Yacht(GeoPoint location, Double heading) {
+ this.location = location;
+ this.heading = heading;
+ this.velocity = 0.0;
+ this.sailIn = false;
+ }
+
+
+ /**
+ * Used in EventTest and RaceTest.
+ *
+ * @param boatName Create a yacht object with name.
+ */
+ 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;
+ this.sailIn = false;
+ }
+
+ /**
+ * Used in BoatGroupTest.
+ *
+ * @param boatName The name of the team sailing the boat
+ * @param boatVelocity The speed of the boat in meters/second
+ * @param shortName A shorter version of the teams name
+ */
+ public Yacht(String boatName, double boatVelocity, String shortName, int id) {
+ this.boatName = boatName;
+ this.velocity = boatVelocity;
+ this.shortName = shortName;
+ this.sourceId = id;
+ this.sailIn = false;
+ }
+
+
+ public Yacht(String boatType, Integer sourceId, String hullID, String shortName,
+ String boatName, String country) {
public Boat(String boatType, Integer sourceID, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
- this.sourceID = sourceID;
+ this.sourceId = sourceId;
this.hullID = hullID;
this.shortName = shortName;
this.boatName = boatName;
this.country = country;
+ this.position = "-";
+ this.sailIn = false;
+ this.location = new GeoPoint(57.670341, 11.826856);
+ this.heading = 120.0;
+ this.velocity = 50000.0;
}
+ /**
+ * @param timeInterval since last update in milliseconds
+ */
+ public void update(Long timeInterval) {
+ if (sailIn) {
+ Double secondsElapsed = timeInterval / 1000000.0;
+ Double thisHeading = ((double) Math.floorMod(heading.longValue(), 360L));
+ Double windSpeedKnots = 0d;
+ Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, thisHeading);
+ velocity = boatSpeedInKnots / ClientPacketParser.MS_TO_KNOTS * 3000;
+ //System.out.println("velocity = " + velocity);
+ Double metersCovered = velocity * secondsElapsed;
+ location = getGeoCoordinate(location, heading, metersCovered);
+ }
+ }
+
+
+ public Double getHeading() {
+ return heading;
+ }
+
+ public void adjustHeading(Double amount) {
+ lastHeading = heading;
+ // TODO: 24/07/17 wmu16 - '%' in java does remainder, we need modulo. All this must be changed here, this is why we have neg values!
+ heading = (heading + amount) % 360.0;
+ }
+
+ public void tackGybe(Double windDirection) {
+ adjustHeading(-2 * ((heading - windDirection) % 360));
+ }
+
+ public void toggleSailIn() {
+ sailIn = !sailIn;
+ }
+
+ public void turnUpwind() {
+ Double normalizedHeading = (heading - GameState.windDirection) % 360;
+ if (normalizedHeading == 0) {
+ if (lastHeading < 180) {
+ adjustHeading(-TURN_STEP);
+ } else {
+ adjustHeading(TURN_STEP);
+ }
+ } else if (normalizedHeading == 180) {
+ if (lastHeading < 180) {
+ adjustHeading(TURN_STEP);
+ } else {
+ adjustHeading(-TURN_STEP);
+ }
+ } else if (normalizedHeading < 180) {
+ adjustHeading(-TURN_STEP);
+ } else {
+ adjustHeading(TURN_STEP);
+ }
+ }
+
+ public void turnDownwind() {
+ Double normalizedHeading = (heading - GameState.windDirection) % 360;
+ if (normalizedHeading == 0) {
+ if (lastHeading < 180) {
+ adjustHeading(TURN_STEP);
+ } else {
+ adjustHeading(-TURN_STEP);
+ }
+ } else if (normalizedHeading == 180) {
+ if (lastHeading < 180) {
+ adjustHeading(-TURN_STEP);
+ } else {
+ adjustHeading(TURN_STEP);
+ }
+ } else if (normalizedHeading < 180) {
+ adjustHeading(TURN_STEP);
+ } else {
+ adjustHeading(-TURN_STEP);
+ }
+ }
+
+
public String getBoatType() {
return boatType;
}
- public Integer getSourceID() {
- return sourceID;
+ public Integer getSourceId() {
+ //@TODO Remove and merge with Creating Game Loop
+ if (sourceId == null) return 0;
+ return sourceId;
}
public String getHullID() {
+ if (hullID == null) return "";
return hullID;
}
@@ -76,6 +227,7 @@ public class Boat {
}
public String getCountry() {
+ if (country == null) return "";
return country;
}
@@ -92,6 +244,10 @@ public class Boat {
}
public void setLegNumber(Integer legNumber) {
+ if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus(
+ sourceId)) {
+ RaceViewController.updateYachtPositionSparkline(this, legNumber);
+ }
this.legNumber = legNumber;
}
@@ -154,12 +310,12 @@ public class Boat {
}
public void setNextMark(Mark nextMark) {
- this.nextMark = nextMark;
- }
+ this.nextMark = nextMark;
+ }
public Mark getNextMark(){
return nextMark;
- }
+ }
public Double getLat() {
return lat;
@@ -183,6 +339,8 @@ public class Boat {
public void setHeading(Double heading) {
this.heading = heading;
+ public Boolean getSailIn() {
+ return sailIn;
}
@Override
@@ -190,6 +348,10 @@ public class Boat {
return boatName;
}
+ public GeoPoint getLocation() {
+ return location;
+ }
+
public void setTimeSinceLastMark (long timeSinceLastMark) {
this.timeSinceLastMark.set(timeSinceLastMark);
}
diff --git a/src/main/java/seng302/model/Player.java b/src/main/java/seng302/model/Player.java
index 9f18e9e9..0d11bc19 100644
--- a/src/main/java/seng302/model/Player.java
+++ b/src/main/java/seng302/model/Player.java
@@ -13,8 +13,9 @@ public class Player {
private Integer lastMarkPassed;
- public Player(Socket socket) {
+ public Player(Socket socket, Yacht yacht) {
this.socket = socket;
+ this.yacht = yacht;
}
public Socket getSocket() {
diff --git a/src/main/java/seng302/model/PolarTable.java b/src/main/java/seng302/model/PolarTable.java
index e24de7e3..338fd856 100644
--- a/src/main/java/seng302/model/PolarTable.java
+++ b/src/main/java/seng302/model/PolarTable.java
@@ -1,6 +1,10 @@
package seng302.model;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
@@ -123,7 +127,7 @@ public final class PolarTable {
*/
public static HashMap getOptimalUpwindVMG(Double thisWindSpeed) {
- Double polarWindSpeed = getClosestMatch(thisWindSpeed);
+ Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
return upwindOptimal.get(polarWindSpeed);
}
@@ -135,30 +139,47 @@ public final class PolarTable {
*/
public static HashMap getOptimalDownwindVMG(Double thisWindSpeed) {
- Double polarWindSpeed = getClosestMatch(thisWindSpeed);
+ Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
return downwindOptimal.get(polarWindSpeed);
}
- private static Double getClosestMatch(Double thisWindSpeed) {
+ public static Double getBoatSpeed(Double thisWindSpeed, Double thisHeading) {
- ArrayList windValues = new ArrayList<>(polarTable.keySet());
+ Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
+ Double polarAngle = getClosestAngleInPolar(polarTable.get(polarWindSpeed), thisHeading);
- Double lowerVal = windValues.get(0);
- Double upperVal = windValues.get(1);
+ return polarTable.get(polarWindSpeed).get(polarAngle);
+ }
- for(int i = 0; i < windValues.size() - 1; i++) {
- lowerVal = windValues.get(i);
- upperVal = windValues.get(i+1);
- if (thisWindSpeed <= upperVal) {
- break;
+
+ public static Double getClosestWindSpeedInPolar(Double thisWindSpeed) {
+ Double smallestDif = Double.POSITIVE_INFINITY;
+ Double closestWind = 0d;
+
+ for (Double polarWindSpeed : polarTable.keySet()) {
+ Double difference = Math.abs(polarWindSpeed - thisWindSpeed);
+ if (difference < smallestDif) {
+ smallestDif = difference;
+ closestWind = polarWindSpeed;
}
}
+ return closestWind;
+ }
- Double lowerDiff = Math.abs(lowerVal - thisWindSpeed);
- Double upperDiff = Math.abs(upperVal - thisWindSpeed);
- return (lowerDiff <= upperDiff) ? lowerVal : upperVal;
+ public static Double getClosestAngleInPolar(HashMap thisWindSpeedPolar, Double thisHeading) {
+ Double smallestDif = Double.POSITIVE_INFINITY;
+ Double closestAngle = 0d;
+
+ for (Double polarAngle : thisWindSpeedPolar.keySet()) {
+ Double difference = Math.abs(polarAngle - thisHeading);
+ if (difference < smallestDif) {
+ smallestDif = difference;
+ closestAngle = polarAngle;
+ }
+ }
+ return closestAngle;
}
}
\ No newline at end of file
diff --git a/src/main/java/seng302/models/xml/Race.java b/src/main/java/seng302/models/xml/Race.java
new file mode 100644
index 00000000..9be61f37
--- /dev/null
+++ b/src/main/java/seng302/models/xml/Race.java
@@ -0,0 +1,53 @@
+package seng302.models.xml;
+
+import seng302.models.Yacht;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A Race object that can be parsed into XML
+ */
+public class Race {
+ private List yachts;
+ private LocalDateTime startTime;
+
+ public Race(){
+ yachts = new ArrayList<>();
+ startTime = LocalDateTime.now();
+ }
+
+ /**
+ * Add a boat to the race
+ * @param yacht The boat to add
+ */
+ public void addBoat(Yacht yacht){
+ yachts.add(yacht);
+ }
+
+ /**
+ * Get a list of boats in the race
+ * @return A List of boats
+ */
+ public List getBoats(){
+ return Collections.unmodifiableList(yachts);
+ }
+
+ /**
+ * Set the time until the race starts
+ * @param seconds The time in seconds until the race starts
+ */
+ public void setRaceStartDelay(Integer seconds){
+ startTime = startTime.plusMinutes(seconds);
+ }
+
+ /**
+ * Get the time the race starts
+ * @return The time the race starts
+ */
+ public String getRaceStartTime(){
+ return startTime.toString();
+ }
+}
diff --git a/src/main/java/seng302/models/xml/Regatta.java b/src/main/java/seng302/models/xml/Regatta.java
new file mode 100644
index 00000000..733b7a0a
--- /dev/null
+++ b/src/main/java/seng302/models/xml/Regatta.java
@@ -0,0 +1,77 @@
+package seng302.models.xml;
+
+/**
+ * A Race regatta that can be parsed into XML
+ */
+public class Regatta {
+ private final Double DEFAULT_ALTITUDE = 0d;
+ private final Integer DEFAULT_REGATTA_ID = 0;
+
+ private Integer id;
+ private String name;
+ private String courseName;
+
+ private Double latitude;
+ private Double longitude;
+ private Double altitude;
+
+ private Integer utcOffset;
+ private Double magneticVariation;
+
+ public Regatta(String name, Double latitude, Double longitude) {
+ this.name = name;
+ this.id = DEFAULT_REGATTA_ID;
+ this.courseName = name;
+
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.altitude = DEFAULT_ALTITUDE;
+
+ this.utcOffset = 0;
+ this.magneticVariation = 0d;
+ }
+
+ public void setMagneticVariation(Double magneticVariation){
+ this.magneticVariation = magneticVariation;
+ }
+
+ public void setUtcOffset(Integer offset){
+ this.utcOffset = offset;
+ }
+
+ /*
+ NOTE!! The following getters must follow the JavaBean standard (getPropertyName()), and must be public.
+ */
+
+ public String getName(){
+ return name;
+ }
+
+ public String getCourseName(){
+ return courseName;
+ }
+
+ public Integer getRegattaId(){
+ return id;
+ }
+
+ public Double getLatitude() {
+ return latitude;
+ }
+
+ public Double getLongitude() {
+ return longitude;
+ }
+
+ public Double getAltitude() {
+ return altitude;
+ }
+
+ public Integer getUtcOffset(){
+ return utcOffset;
+ }
+
+ public Double getMagneticVariation(){
+ return magneticVariation;
+ }
+}
diff --git a/src/main/java/seng302/models/xml/XMLGenerator.java b/src/main/java/seng302/models/xml/XMLGenerator.java
new file mode 100644
index 00000000..04a5b5fb
--- /dev/null
+++ b/src/main/java/seng302/models/xml/XMLGenerator.java
@@ -0,0 +1,161 @@
+package seng302.models.xml;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import org.apache.commons.io.IOUtils;
+import seng302.server.messages.XMLMessageSubType;
+
+import java.io.*;
+import java.net.URISyntaxException;
+
+/**
+ * An XML generator to generate the Race, Boat, and Regatta XML dynamically
+ */
+public class XMLGenerator {
+ private static final String XML_TEMPLATE_DIR = "/server_config/xml_templates";
+ private static final String REGATTA_TEMPLATE_NAME = "regatta.ftlh";
+ private static final String BOATS_TEMPLATE_NAME = "boats.ftlh";
+ private static final String RACE_TEMPLATE_NAME = "race.ftlh";
+ private Configuration configuration;
+ private Regatta regatta;
+ private Race race;
+
+ /**
+ * Set up a configuration instance for Apache Freemake
+ */
+ private void setupConfiguration() {
+ configuration = new Configuration(Configuration.VERSION_2_3_26);
+
+ try {
+ configuration.setClassForTemplateLoading(getClass(), XML_TEMPLATE_DIR);
+ } catch (NullPointerException e){
+ System.out.println("[FATAL] Server could not load XML Template directory, ensure this directory isn't empty");
+ }
+ }
+
+ /**
+ * Create an instance of the XML Generator
+ */
+ public XMLGenerator(){
+ setupConfiguration();
+ }
+
+ /**
+ * Set the race regatta to send to players
+ * Note: This must be set before a regatta message can be generated
+ * @param regatta The race regatta
+ */
+ public void setRegatta(Regatta regatta){
+ this.regatta = regatta;
+ }
+
+ /**
+ * Set the race to send to players
+ * Note: This must be set before a boat or race message can be generated
+ * @param race The race
+ */
+ public void setRace(Race race){
+ this.race = race;
+ }
+
+ /**
+ * Parse an XML template and generate the output as a string
+ * @param templateName The templates file name
+ * @param type The XML message sub type
+ */
+ private String parseToXmlString(String templateName, XMLMessageSubType type) throws IOException, TemplateException {
+ Template template;
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ OutputStreamWriter writer = new OutputStreamWriter(os);
+
+ template = configuration.getTemplate(templateName);
+
+ switch (type) {
+ case REGATTA:
+ template.process(regatta, writer);
+ break;
+
+ case BOAT:
+ template.process(race, writer);
+ break;
+
+ case RACE:
+ template.process(race, writer);
+ break;
+
+ default:
+ throw new UnsupportedOperationException();
+ }
+
+ try {
+ return os.toString("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ System.out.println("[FATAL] UTF-8 Not supported");
+ return null;
+ }
+ }
+
+ /**
+ * Get the race regatta as a string
+ * Note: Regatta must be set before calling this
+ * @return String containing the regatta XML, null if there was an error
+ */
+ public String getRegattaAsXml(){
+ String result = null;
+
+ if (regatta == null) return null;
+
+ try {
+ result = parseToXmlString(REGATTA_TEMPLATE_NAME, XMLMessageSubType.REGATTA);
+ } catch (TemplateException e) {
+ System.out.println("[FATAL] Error parsing regatta");
+ } catch (IOException e) {
+ System.out.println("[FATAL] Error reading regatta");
+ }
+
+ return result;
+ }
+
+ /**
+ * Get the boats XML as a string
+ * Note: Race must be set before calling this
+ * @return String containing the boats XML, null if there was an error
+ */
+ public String getBoatsAsXml() {
+ String result = null;
+
+ if (race == null) return null;
+
+ try {
+ result = parseToXmlString(BOATS_TEMPLATE_NAME, XMLMessageSubType.BOAT);
+ } catch (TemplateException e) {
+ System.out.println("[FATAL] Error parsing boats");
+ } catch (IOException e) {
+ System.out.println("[FATAL] Error reading boats");
+ }
+
+ return result;
+ }
+
+ /**
+ * Get the race XML as a string
+ * Note: Race must be set before calling this
+ * @return String containing the race XML, null if there was an error
+ */
+ public String getRaceAsXml() {
+ String result = null;
+
+ if (race == null) return null;
+
+ try {
+ result = parseToXmlString(RACE_TEMPLATE_NAME, XMLMessageSubType.RACE);
+ } catch (TemplateException e) {
+ System.out.println("[FATAL] Error parsing race");
+ } catch (IOException e) {
+ System.out.println("[FATAL] Error reading race");
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/seng302/server/messages/BoatActionMessage.java b/src/main/java/seng302/server/messages/BoatActionMessage.java
index 4a53f40a..cf4ea918 100644
--- a/src/main/java/seng302/server/messages/BoatActionMessage.java
+++ b/src/main/java/seng302/server/messages/BoatActionMessage.java
@@ -18,7 +18,7 @@ public class BoatActionMessage extends Message{
allocateBuffer();
writeHeaderToBuffer();
// Write message fields
- putInt((int) BoatActionType.getBoatPacketType(actionType), 1);
+ putInt(actionType.getValue(), 1);
writeCRC();
rewind();
diff --git a/src/main/java/seng302/server/messages/BoatActionType.java b/src/main/java/seng302/server/messages/BoatActionType.java
index e323b6fe..f8318af7 100644
--- a/src/main/java/seng302/server/messages/BoatActionType.java
+++ b/src/main/java/seng302/server/messages/BoatActionType.java
@@ -1,5 +1,8 @@
package seng302.server.messages;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Created by kre39 on 12/07/17.
*/
@@ -12,31 +15,24 @@ public enum BoatActionType {
UPWIND(5),
DOWNWIND(6);
- private int type;
+ 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 int getType(){
- return this.type;
+ public static BoatActionType getType(int value) {
+ return intToTypeMap.get(value);
}
- 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;
+ public int getValue() {
+ return this.type;
}
}
diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java
index 2375ba17..0310216e 100644
--- a/src/main/java/seng302/server/messages/RaceStatusMessage.java
+++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java
@@ -9,12 +9,14 @@ public class RaceStatusMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS;
private final int MESSAGE_VERSION = 2; //Always set to 1
private final int MESSAGE_BASE_SIZE = 24;
+ private final double windDirFactor = 0x4000 / 90;
+
private long currentTime;
private long raceId;
private RaceStatus raceStatus;
private long expectedStartTime;
- private WindDirection raceWindDirection;
+ private double raceWindDirection;
private long windSpeed;
private long numBoatsInRace;
private RaceType raceType;
@@ -33,13 +35,13 @@ public class RaceStatusMessage extends Message{
* @param sourceId The source of this message
* @param boats A list of boat status sub messages
*/
- public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection,
+ public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, double raceWindDirection,
long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List boats){
currentTime = System.currentTimeMillis();
this.raceId = raceId;
this.raceStatus = raceStatus;
this.expectedStartTime = expectedStartTime;
- this.raceWindDirection = raceWindDirection;
+ this.raceWindDirection = raceWindDirection * windDirFactor;
this.windSpeed = windSpeed;
this.numBoatsInRace = numBoatsInRace;
this.raceType = raceType;
@@ -55,7 +57,7 @@ public class RaceStatusMessage extends Message{
putInt((int) raceId, 4);
putByte((byte) raceStatus.getCode());
putInt(expectedStartTime, 6);
- putInt((int) raceWindDirection.getCode(), 2);
+ putInt((int) this.raceWindDirection, 2);
putInt((int) windSpeed, 2);
putByte((byte) numBoatsInRace);
putByte((byte) raceType.getCode());
diff --git a/src/main/java/seng302/server/messages/WindDirection.java b/src/main/java/seng302/server/messages/WindDirection.java
deleted file mode 100644
index c0b8d767..00000000
--- a/src/main/java/seng302/server/messages/WindDirection.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package seng302.server.messages;
-
-/**
- * Enum containing the supported wind directions
- */
-public enum WindDirection {
- NORTH(0x0000L),
- EAST(0x4000L),
- SOUTH(0x8000L);
-
- private long code;
-
- WindDirection(long code) {
- this.code = code;
- }
-
- public long getCode() {
- return code;
- }
-}
diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java
index d67ac833..75c7ab1e 100644
--- a/src/main/java/seng302/visualiser/ClientToServerThread.java
+++ b/src/main/java/seng302/visualiser/ClientToServerThread.java
@@ -9,10 +9,13 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.net.UnknownHostException;
+import java.time.LocalDateTime;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import seng302.model.stream.packets.StreamPacket;
+import seng302.models.stream.packets.StreamPacket;
import seng302.server.messages.BoatActionMessage;
import seng302.server.messages.Message;
@@ -23,28 +26,45 @@ public class ClientToServerThread extends Thread {
private Queue streamPackets = new ConcurrentLinkedQueue<>();
private List listeners = new ArrayList<>();
+public class ClientToServerThread implements Runnable {
+
+ private static final int LOG_LEVEL = 1;
+
+ private Thread thread;
+
+ private Integer ourID;
+
private Socket socket;
private InputStream is;
private OutputStream os;
- private 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();
+ public ClientToServerThread(String ipAddress, Integer portNumber) throws Exception{
+ socket = new Socket(ipAddress, portNumber);
+ is = socket.getInputStream();
+ os = socket.getOutputStream();
+
+ Integer allocatedID = threeWayHandshake();
+ if (allocatedID != null) {
+ ourID = allocatedID;
+ clientLog("Successful handshake. Allocated ID: " + ourID, 1);
+ ClientState.setClientSourceId(String.valueOf(ourID));
+ } else {
+ clientLog("Unsuccessful handshake", 1);
+ closeSocket();
+ return;
}
+
+ thread = new Thread(this);
+ thread.start();
+
}
- static void serverLog(String message, int logLevel){
+ static void clientLog(String message, int logLevel){
if(logLevel <= LOG_LEVEL){
- System.out.println("[SERVER] " + message);
+ System.out.println("[CLIENT " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
}
}
@@ -52,8 +72,18 @@ public class ClientToServerThread extends Thread {
int sync1;
int sync2;
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
- while(true) {
+ while(ClientState.isConnectedToHost()) {
try {
+ //Perform a write if it is time to as delegated by the MainServerThread
+ if (updateClient) {
+ // TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream
+// try {
+// GameState.outputState(os);
+// } catch (IOException e) {
+// System.out.println("IO error in server thread upon writing to output stream");
+// }
+ updateClient = false;
+ }
crcBuffer = new ByteArrayOutputStream();
sync1 = readByte();
sync2 = readByte();
@@ -74,15 +104,42 @@ public class ClientToServerThread extends Thread {
for (ClientSocketListener csl : listeners)
csl.newPacket(new StreamPacket(type, payloadLength, timeStamp, payload));
} else {
- System.err.println("Packet has been dropped");
+ clientLog("Packet has been dropped", 1);
}
}
} catch (Exception e) {
closeSocket();
+ e.printStackTrace();
return;
}
}
+ closeSocket();
+ clientLog("Disconnected from server", 0);
+ }
+
+ /**
+ * Listens for an allocated sourceID and returns it to the server if recieved
+ * @return the sourceID allocated to us by the server
+ */
+ private Integer threeWayHandshake() {
+ Integer ourSourceID = null;
+ while (true) {
+ try {
+ ourSourceID = is.read();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if (ourSourceID != null) {
+ try {
+ os.write(ourSourceID);
+ return ourSourceID;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+ }
}
/**
@@ -92,6 +149,7 @@ public class ClientToServerThread extends Thread {
try {
os.write(boatActionMessage.getBuffer());
} catch (IOException e) {
+ clientLog("COULD NOT WRITE TO SERVER", 0);
e.printStackTrace();
}
}
@@ -140,4 +198,8 @@ public class ClientToServerThread extends Thread {
readByte();
}
}
- }
+
+ public Thread getThread() {
+ return thread;
+ }
+}
diff --git a/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java b/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java
index e08521d4..7432853d 100644
--- a/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java
+++ b/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java
@@ -1,4 +1,4 @@
-package seng302.visualiser.controllers;
+package seng302.controllers;
import java.io.IOException;
import java.net.URL;
@@ -15,6 +15,9 @@ import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
+import seng302.client.ClientPacketParser;
+import seng302.models.Yacht;
+import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
import seng302.model.Boat;
import seng302.model.stream.parsers.StreamParser;
import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Participant;
@@ -24,15 +27,15 @@ public class FinishScreenViewController implements Initializable {
@FXML
private GridPane finishScreenGridPane;
@FXML
- private TableView finishOrderTable;
+ private TableView finishOrderTable;
@FXML
- private TableColumn posCol;
+ private TableColumn posCol;
@FXML
- private TableColumn boatNameCol;
+ private TableColumn boatNameCol;
@FXML
- private TableColumn shortNameCol;
+ private TableColumn shortNameCol;
@FXML
- private TableColumn countryCol;
+ private TableColumn countryCol;
@Override
public void initialize(URL location, ResourceBundle resources) {
@@ -41,7 +44,7 @@ public class FinishScreenViewController implements Initializable {
finishOrderTable.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// set up data for table
- ObservableList data = FXCollections.observableArrayList();
+ ObservableList data = FXCollections.observableArrayList();
finishOrderTable.setItems(data);
// setting table col data
@@ -67,7 +70,7 @@ public class FinishScreenViewController implements Initializable {
}
// add data to table
- for (Boat boat : StreamParser.getBoatsPos().values()) {
+ for (Yacht boat : StreamParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) {
data.add(boat);
}
diff --git a/src/main/java/seng302/visualiser/controllers/LobbyController.java b/src/main/java/seng302/visualiser/controllers/LobbyController.java
index efcb388a..00a9dec7 100644
--- a/src/main/java/seng302/visualiser/controllers/LobbyController.java
+++ b/src/main/java/seng302/visualiser/controllers/LobbyController.java
@@ -1,39 +1,92 @@
package seng302.visualiser.controllers;
import java.io.IOException;
+import java.io.InputStream;
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 java.util.*;
+
+import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
+import javafx.scene.control.Button;
import javafx.scene.control.ListView;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
+import javafx.scene.media.Media;
+import javafx.scene.media.MediaPlayer;
import javafx.scene.text.Text;
+import seng302.client.ClientState;
+import seng302.client.ClientStateQueryingRunnable;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
+import seng302.gameServer.MainServerThread;
/**
* A class describing the actions of the lobby screen
* Created by wmu16 on 10/07/17.
*/
-public class LobbyController implements Initializable{
-
- @FXML
- private ListView competitorsListView;
+public class LobbyController implements Initializable, Observer{
@FXML
private GridPane lobbyScreen;
@FXML
private Text lobbyIpText;
+ @FXML
+ private Button readyButton;
+ @FXML
+ private ListView firstListView;
+ @FXML
+ private ListView secondListView;
+ @FXML
+ private ListView thirdListView;
+ @FXML
+ private ListView fourthListView;
+ @FXML
+ private ListView fifthListView;
+ @FXML
+ private ListView sixthListView;
+ @FXML
+ private ListView seventhListView;
+ @FXML
+ private ListView eighthListView;
+ @FXML
+ private ImageView firstImageView;
+ @FXML
+ private ImageView secondImageView;
+ @FXML
+ private ImageView thirdImageView;
+ @FXML
+ private ImageView fourthImageView;
+ @FXML
+ private ImageView fifthImageView;
+ @FXML
+ private ImageView sixthImageView;
+ @FXML
+ private ImageView seventhImageView;
+ @FXML
+ private ImageView eighthImageView;
- private static ObservableList competitors;
+ private static List> competitors = new ArrayList<>();
+ private static ObservableList firstCompetitor = FXCollections.observableArrayList();
+ private static ObservableList secondCompetitor = FXCollections.observableArrayList();
+ private static ObservableList thirdCompetitor = FXCollections.observableArrayList();
+ private static ObservableList fourthCompetitor = FXCollections.observableArrayList();
+ private static ObservableList fifthCompetitor = FXCollections.observableArrayList();
+ private static ObservableList sixthCompetitor = FXCollections.observableArrayList();
+ private static ObservableList seventhCompetitor = FXCollections.observableArrayList();
+ private static ObservableList eighthCompetitor = FXCollections.observableArrayList();
+ private ClientStateQueryingRunnable clientStateQueryingRunnable;
+
+ private Boolean switchedPane = false;
+ private MainServerThread mainServerThread;
private void setContentPane(String jfxUrl) {
try {
@@ -52,58 +105,162 @@ public class LobbyController implements Initializable{
@Override
public void initialize(URL location, ResourceBundle resources) {
- lobbyIpText.setText("Lobby Host IP: " + getLocalHostIp());
+ if (ClientState.isHost()) {
+ lobbyIpText.setText("Lobby Host IP: " + ClientState.getHostIp());
+ readyButton.setDisable(false);
+ }
+ else {
+ lobbyIpText.setText("Connected to IP: ");
+ readyButton.setDisable(true);
+ }
+ initialiseListView();
+// initialiseLobbyControllerThread();
+ initialiseImageView(); // parrot gif init
+
+ // set up client state query thread, so that when it receives the race-started packet
+ // it can switch to the race view
+ ClientStateQueryingRunnable clientStateQueryingRunnable = new ClientStateQueryingRunnable();
+ clientStateQueryingRunnable.addObserver(this);
+ Thread clientStateQueryingThread = new Thread(clientStateQueryingRunnable, "Client State querying thread");
+ clientStateQueryingThread.setDaemon(true);
+ clientStateQueryingThread.start();
}
- 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();
- }
+ @Override
+ public void update(Observable o, Object arg) {
+ Platform.runLater(new Runnable() {
+ @Override
+ public void run() {
+ if (arg.equals("game started") && !switchedPane) {
+ switchToRaceView();
+ }
+ if (arg.equals(("update players"))) {
+ initialiseListView();
}
}
- } catch (Exception e) {
- e.printStackTrace();
+ });
+ }
+
+ private void initialiseListView() {
+ firstListView.getItems().clear();
+ secondListView.getItems().clear();
+ thirdListView.getItems().clear();
+ fourthListView.getItems().clear();
+ fifthListView.getItems().clear();
+ sixthListView.getItems().clear();
+ seventhListView.getItems().clear();
+ eighthListView.getItems().clear();
+
+ competitors = new ArrayList<>();
+ Collections.addAll(competitors, firstCompetitor, secondCompetitor, thirdCompetitor,
+ fourthCompetitor, fifthCompetitor, sixthCompetitor, seventhCompetitor, eighthCompetitor);
+
+ for (ObservableList ol : competitors) {
+ ol.removeAll();
}
- if (ipAddress == null) {
- System.out.println("[HOST] Cannot obtain local host ip address.");
+
+ firstCompetitor.add(ClientState.getClientSourceId());
+
+ int competitorIndex = 1;
+ for (Integer yachtId : ClientState.getBoats().keySet()) {
+ // break if there are more than 7 competitors
+ if (competitorIndex >= 8) {
+ break;
+ }
+ if (!yachtId.equals(Integer.parseInt(ClientState.getClientSourceId()))) {
+ competitors.get(competitorIndex).add(String.valueOf(yachtId));
+ competitorIndex++;
+ }
}
- return ipAddress;
+
+ firstListView.setItems(firstCompetitor);
+ secondListView.setItems(secondCompetitor);
+ thirdListView.setItems(thirdCompetitor);
+ fourthListView.setItems(fourthCompetitor);
+ fifthListView.setItems(fifthCompetitor);
+ sixthListView.setItems(sixthCompetitor);
+ seventhListView.setItems(seventhCompetitor);
+ eighthListView.setItems(eighthCompetitor);
+ }
+
+ private void initialiseLobbyControllerThread() {
+ Thread thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Platform.runLater(new Runnable() {
+ @Override
+ public void run() {
+
+ }
+ });
+ }
+ });
+ thread.start();
+ }
+
+ private void initialiseImageView() {
+ Image image1 = new Image(getClass().getResourceAsStream("/ParrotGif/alistair.gif"));
+ firstImageView.setImage(image1);
+ Image image2 = new Image(getClass().getResourceAsStream("/ParrotGif/calum.gif"));
+ secondImageView.setImage(image2);
+ Image image3 = new Image(getClass().getResourceAsStream("/ParrotGif/haoming.gif"));
+ thirdImageView.setImage(image3);
+ Image image4 = new Image(getClass().getResourceAsStream("/ParrotGif/kusal.gif"));
+ fourthImageView.setImage(image4);
+ Image image5 = new Image(getClass().getResourceAsStream("/ParrotGif/michael.gif"));
+ fifthImageView.setImage(image5);
+ Image image6 = new Image(getClass().getResourceAsStream("/ParrotGif/peter.gif"));
+ sixthImageView.setImage(image6);
+ Image image7 = new Image(getClass().getResourceAsStream("/ParrotGif/ryan.gif"));
+ seventhImageView.setImage(image7);
+ Image image8 = new Image(getClass().getResourceAsStream("/ParrotGif/will.gif"));
+ eighthImageView.setImage(image8);
}
@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
+ ClientState.setConnectedToHost(false);
}
-
@FXML
public void readyButtonPressed() {
+// setContentPane("/views/RaceView.fxml");
+ playTheme();
GameState.setCurrentStage(GameStages.RACING);
- setContentPane("/views/RaceView.fxml");
+ mainServerThread.startGame();
+ }
+
+ private static MediaPlayer mediaPlayer;
+
+ private void playTheme() {
+ Random random = new Random(System.currentTimeMillis());
+ Integer rand = random.nextInt();
+ if(rand == 10) {
+ URL file = getClass().getResource("/music/Disturbed - down with the sickness.mp3");
+ Media hit = new Media(file.toString());
+ mediaPlayer = new MediaPlayer(hit);
+ mediaPlayer.play();
+ } else if(rand == 9) {
+ URL file = getClass().getResource("/music/Owl City - Fireflies.mp3");
+ Media hit = new Media(file.toString());
+ mediaPlayer = new MediaPlayer(hit);
+ mediaPlayer.play();
+ }
+ }
+
+ private void switchToRaceView() {
+ if (!switchedPane) {
+ switchedPane = true;
+ setContentPane("/views/RaceView.fxml");
+ }
+ }
+
+ public void setMainServerThread(MainServerThread mainServerThread) {
+ this.mainServerThread = mainServerThread;
}
}
diff --git a/src/main/java/seng302/visualiser/controllers/StartScreenController.java b/src/main/java/seng302/visualiser/controllers/StartScreenController.java
index 90e2f2a6..27335485 100644
--- a/src/main/java/seng302/visualiser/controllers/StartScreenController.java
+++ b/src/main/java/seng302/visualiser/controllers/StartScreenController.java
@@ -1,11 +1,18 @@
package seng302.visualiser.controllers;
+import java.net.Inet4Address;
+import java.net.NetworkInterface;
+import java.util.Enumeration;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
+import seng302.client.ClientState;
+import seng302.client.ClientToServerThread;
import seng302.visualiser.ClientToServerThread;
import seng302.gameServer.GameState;
import seng302.gameServer.MainServerThread;
@@ -23,6 +30,8 @@ public class StartScreenController {
@FXML
private TextField ipTextField;
@FXML
+ private TextField portTextField;
+ @FXML
private GridPane startScreen2;
/**
@@ -38,6 +47,7 @@ public class StartScreenController {
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl));
contentPane.getChildren().addAll((Pane) fxmlLoader.load());
+
return fxmlLoader.getController();
} catch (IOException e) {
e.printStackTrace();
@@ -49,7 +59,8 @@ public class StartScreenController {
/**
* ATTEMPTS TO:
* Sets up a new game state with your IP address as designated as the host.
- * Starts a thread to listen for incoming connections
+ * Starts a thread to listen for incoming connections.
+ * Starts a client to server thread and connects to own ip.
* Switches to the lobby screen
*/
@FXML
@@ -62,26 +73,92 @@ public class StartScreenController {
// controller.setClientToServerThread(clientToServerThread);
clientToServerThread.start();
// get the lobby controller so that we can pass the game server thread to it
- setContentPane("/views/LobbyView.fxml");
-
- } catch (UnknownHostException e) {
- System.err.println("COULD NOT FIND YOUR IP ADDRESS!");
+ new GameState(getLocalHostIp());
+ MainServerThread mainServerThread = new MainServerThread();
+ ClientState.setHost(true);
+ // host will connect and handshake to itself after setting up the server
+ // TODO: 24/07/17 wmu16 - Make port number some static global type constant?
+ ClientToServerThread clientToServerThread = new ClientToServerThread(ClientState.getHostIp(), 4942);
+ ClientState.setConnectedToHost(true);
+ controller.setClientToServerThread(clientToServerThread);
+ LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml");
+ lobbyController.setMainServerThread(mainServerThread);
+ } catch (Exception e) {
+ Alert alert = new Alert(AlertType.ERROR);
+ alert.setHeaderText("Cannot host");
+ alert.setContentText("Oops, failed to host, try to restart.");
+ alert.showAndWait();
e.printStackTrace();
}
+
}
-
+ /**
+ * ATTEMPTS TO:
+ * Connect to an ip address and port using the ip and port specified on start screen.
+ * Starts a Client To Server Thread to maintain connection to host.
+ * Switch view to lobby view.
+ */
@FXML
public void connectButtonPressed() {
// TODO: 10/07/17 wmu16 - Finish function
- String ipAddress = ipTextField.getText().trim().toLowerCase();
try {
- ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, 4950);
- clientToServerThread.start();
+ String ipAddress = ipTextField.getText().trim().toLowerCase();
+ Integer port = Integer.valueOf(portTextField.getText().trim());
+
+ ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, port);
+ ClientState.setHost(false);
+ ClientState.setConnectedToHost(true);
+
+ controller.setClientToServerThread(clientToServerThread);
setContentPane("/views/LobbyView.fxml");
- } catch (Exception e){
- e.printStackTrace();
+ } catch (Exception e) {
+ Alert alert = new Alert(AlertType.ERROR);
+ alert.setHeaderText("Cannot reach the host");
+ alert.setContentText("Please check your host IP address.");
+ alert.showAndWait();
}
}
+
+ public void setController(Controller controller) {
+ this.controller = controller;
+ }
+
+ /**
+ * Gets the local host ip address and sets this ip to ClientState.
+ * Only runs by the host.
+ *
+ * @return the localhost ip address
+ */
+ private String getLocalHostIp() {
+ String ipAddress = null;
+ try {
+ Enumeration 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.");
+ }
+ ClientState.setHostIp(ipAddress);
+ return ipAddress;
+ }
}
diff --git a/src/main/resources/ParrotGif/alistair.gif b/src/main/resources/ParrotGif/alistair.gif
new file mode 100644
index 00000000..ce003789
Binary files /dev/null and b/src/main/resources/ParrotGif/alistair.gif differ
diff --git a/src/main/resources/ParrotGif/calum.gif b/src/main/resources/ParrotGif/calum.gif
new file mode 100644
index 00000000..03d495ed
Binary files /dev/null and b/src/main/resources/ParrotGif/calum.gif differ
diff --git a/src/main/resources/ParrotGif/haoming.gif b/src/main/resources/ParrotGif/haoming.gif
new file mode 100644
index 00000000..33b26de6
Binary files /dev/null and b/src/main/resources/ParrotGif/haoming.gif differ
diff --git a/src/main/resources/ParrotGif/kusal.gif b/src/main/resources/ParrotGif/kusal.gif
new file mode 100644
index 00000000..1372928c
Binary files /dev/null and b/src/main/resources/ParrotGif/kusal.gif differ
diff --git a/src/main/resources/ParrotGif/michael.gif b/src/main/resources/ParrotGif/michael.gif
new file mode 100644
index 00000000..83ea1dff
Binary files /dev/null and b/src/main/resources/ParrotGif/michael.gif differ
diff --git a/src/main/resources/ParrotGif/parrot.gif b/src/main/resources/ParrotGif/parrot.gif
new file mode 100644
index 00000000..458ad859
Binary files /dev/null and b/src/main/resources/ParrotGif/parrot.gif differ
diff --git a/src/main/resources/ParrotGif/peter.gif b/src/main/resources/ParrotGif/peter.gif
new file mode 100644
index 00000000..470b46cf
Binary files /dev/null and b/src/main/resources/ParrotGif/peter.gif differ
diff --git a/src/main/resources/ParrotGif/ryan.gif b/src/main/resources/ParrotGif/ryan.gif
new file mode 100644
index 00000000..b518c777
Binary files /dev/null and b/src/main/resources/ParrotGif/ryan.gif differ
diff --git a/src/main/resources/ParrotGif/will.gif b/src/main/resources/ParrotGif/will.gif
new file mode 100644
index 00000000..e7b762de
Binary files /dev/null and b/src/main/resources/ParrotGif/will.gif differ
diff --git a/src/main/resources/config/course.xml b/src/main/resources/config/course.xml
index cec726ad..180f692a 100644
--- a/src/main/resources/config/course.xml
+++ b/src/main/resources/config/course.xml
@@ -12,8 +12,8 @@
Start2
- 57.6706330
- 11.8281330
+ 57.6703330
+ 11.8271333
123
diff --git a/src/main/resources/music/Disturbed - down with the sickness.mp3 b/src/main/resources/music/Disturbed - down with the sickness.mp3
new file mode 100644
index 00000000..375b140b
Binary files /dev/null and b/src/main/resources/music/Disturbed - down with the sickness.mp3 differ
diff --git a/src/main/resources/music/Owl City - Fireflies.mp3 b/src/main/resources/music/Owl City - Fireflies.mp3
new file mode 100644
index 00000000..fce20269
Binary files /dev/null and b/src/main/resources/music/Owl City - Fireflies.mp3 differ
diff --git a/src/main/resources/server_config/boats1.xml b/src/main/resources/server_config/boats1.xml
new file mode 100644
index 00000000..401e7bf6
--- /dev/null
+++ b/src/main/resources/server_config/boats1.xml
@@ -0,0 +1,171 @@
+
+
+ 2015-08-28T17:32:59+0100
+ 12
+ 219
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/server_config/boats2.xml b/src/main/resources/server_config/boats2.xml
new file mode 100644
index 00000000..c7255771
--- /dev/null
+++ b/src/main/resources/server_config/boats2.xml
@@ -0,0 +1,161 @@
+
+
+ 2015-08-28T17:32:59+0100
+ 12
+ 219
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/server_config/boats3.xml b/src/main/resources/server_config/boats3.xml
new file mode 100644
index 00000000..401e7bf6
--- /dev/null
+++ b/src/main/resources/server_config/boats3.xml
@@ -0,0 +1,171 @@
+
+
+ 2015-08-28T17:32:59+0100
+ 12
+ 219
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/server_config/xml_templates/boats.ftlh b/src/main/resources/server_config/xml_templates/boats.ftlh
new file mode 100644
index 00000000..f9a1e2d0
--- /dev/null
+++ b/src/main/resources/server_config/xml_templates/boats.ftlh
@@ -0,0 +1,22 @@
+
+ 2012-05-17T07:49:40+0200
+ 12
+
+
+
+
+
+
+
+ <#-- Not used -->
+
+
+ <#list boats as boat>
+
+
+
+
+ #list>
+
+
\ No newline at end of file
diff --git a/src/main/resources/server_config/xml_templates/race.ftlh b/src/main/resources/server_config/xml_templates/race.ftlh
new file mode 100644
index 00000000..4349d2e3
--- /dev/null
+++ b/src/main/resources/server_config/xml_templates/race.ftlh
@@ -0,0 +1,86 @@
+
+
+ ${raceStartTime}
+
+ 15082901
+ Fleet
+
+
+ <#list boats as boat>
+
+ #list>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/server_config/xml_templates/regatta.ftlh b/src/main/resources/server_config/xml_templates/regatta.ftlh
new file mode 100644
index 00000000..25543c15
--- /dev/null
+++ b/src/main/resources/server_config/xml_templates/regatta.ftlh
@@ -0,0 +1,11 @@
+
+
+ ${regattaId}
+ ${name}
+ ${courseName}
+ ${latitude}
+ ${longitude}
+ ${altitude}
+ ${utcOffset}
+ ${magneticVariation}
+
\ No newline at end of file
diff --git a/src/main/resources/views/LobbyView.fxml b/src/main/resources/views/LobbyView.fxml
index 454f3ff6..e867a519 100644
--- a/src/main/resources/views/LobbyView.fxml
+++ b/src/main/resources/views/LobbyView.fxml
@@ -1,5 +1,6 @@
+
@@ -13,14 +14,14 @@
-
+
-
-
+
+
@@ -28,7 +29,7 @@
-
+
@@ -37,31 +38,100 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml
index b61c0451..bc6d1563 100644
--- a/src/main/resources/views/RaceView.fxml
+++ b/src/main/resources/views/RaceView.fxml
@@ -35,6 +35,11 @@
+
+
+
+
+
diff --git a/src/main/resources/views/StartScreenView.fxml b/src/main/resources/views/StartScreenView.fxml
index db38b3e3..a0489a2c 100644
--- a/src/main/resources/views/StartScreenView.fxml
+++ b/src/main/resources/views/StartScreenView.fxml
@@ -15,8 +15,8 @@
-
-
+
+
@@ -33,10 +33,13 @@
-
-
+
+
+
+
@@ -44,5 +47,13 @@
+
+
+
+
+
+
+
+
diff --git a/src/test/java/seng302/models/YachtTest.java b/src/test/java/seng302/models/YachtTest.java
new file mode 100644
index 00000000..ab467522
--- /dev/null
+++ b/src/test/java/seng302/models/YachtTest.java
@@ -0,0 +1,27 @@
+package seng302.models;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import seng302.utilities.GeoPoint;
+
+public class YachtTest {
+
+ Double windDir;
+ Double windSpd;
+ List yachts = new ArrayList();
+
+ @Before
+ public void setUp() {
+ PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
+ windDir = 90d;
+ windSpd = 10d;
+
+ yachts.add(new Yacht("Yacht 1", "Y1", new GeoPoint(-30.0, 20.0), 160.0));
+ yachts.add(new Yacht("Yacht 2", "Y2", new GeoPoint(-40.0, -20.0), 100.0));
+ yachts.add(new Yacht("Yacht 3", "Y3", new GeoPoint(-35.0, -15.5), 20.0));
+ }
+
+}