diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 00000000..f0fdafc3 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,17 @@ +engines: + pmd: + enabled: true + channel: "beta" + + fixme: + enabled: true + config: + strings: + - FIXME + - TODO + - BUG + - FIX + +ratings: + paths: + - "**.java" diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index c796998a..c97b5be7 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -14,7 +14,7 @@ public class App extends Application { @Override public void start(Stage primaryStage) throws Exception { - PolarTable.parsePolarFile(getClass().getResource("/config/acc_polars.csv").getFile()); + PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml")); primaryStage.setTitle("RaceVision"); @@ -29,51 +29,13 @@ public class App extends Application { StreamReceiver.noMoreBytes(); System.exit(0); }); + + + } public static void main(String[] args) { - StreamReceiver sr = null; - - new ServerThread("Racevision Test Server"); - - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (args.length == 1 && args[0].equals("-standalone")) { - return; - } - - if (args.length == 3 && args[0].equals("-server")) { - - sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream"); - - } else if (args.length == 2 && args[0].equals("-server")) { - switch (args[1]) { - case "internal": - sr = new StreamReceiver("localhost", 4949, "RaceStream"); - break; - case "staffserver": - sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); - break; - case "official": - sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); - break; - } - } - //Change the StreamReceiver in this else block to change the default data source. - else{ - sr = new StreamReceiver("localhost", 4949, "RaceStream"); - } - - sr.start(); - StreamParser streamParser = new StreamParser("StreamParser"); - streamParser.start(); - launch(args); - } } diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index d368a53c..2bb600f6 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -1,8 +1,15 @@ package seng302.controllers; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.PriorityBlockingQueue; import javafx.animation.AnimationTimer; import javafx.beans.property.SimpleDoubleProperty; import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; import javafx.geometry.Point2D; import javafx.scene.Group; import javafx.scene.canvas.Canvas; @@ -10,6 +17,8 @@ import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.ImageView; import javafx.scene.input.KeyEvent; import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; @@ -90,7 +99,7 @@ public class CanvasController { VERTICAL } - public void setup(RaceViewController raceViewController){ + public void setup(RaceViewController raceViewController) { this.raceViewController = raceViewController; } @@ -113,7 +122,7 @@ public class CanvasController { } - public void initializeCanvas (){ + public void initializeCanvas() { gc = canvas.getGraphicsContext2D(); gc.setGlobalAlpha(0.5); @@ -163,11 +172,29 @@ public class CanvasController { } if (StreamParser.isRaceFinished()) { this.stop(); + switchToFinishScreen(); } } }; } + private void switchToFinishScreen() { + try { + // canvas view -> anchor pane -> grid pane -> main view + GridPane gridPane = (GridPane) canvasPane.getParent().getParent(); + AnchorPane contentPane = (AnchorPane) gridPane.getParent(); + contentPane.getChildren().removeAll(); + contentPane.getChildren().clear(); + contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + contentPane.getChildren().addAll( + (Pane) FXMLLoader.load(getClass().getResource("/views/FinishScreenView.fxml"))); + } catch (javafx.fxml.LoadException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + /** * First find the top right and bottom left points' geo locations, then retrieve * map from google to display on image view. - Haoming 22/5/2017 @@ -176,19 +203,29 @@ public class CanvasController { findMetersPerPixel(); Point2D topLeftPoint = findScaledXY(maxLatPoint.getLatitude(), minLonPoint.getLongitude()); // distance from top left extreme to panel origin (top left corner) - double distanceFromTopLeftToOrigin = Math.sqrt(Math.pow(topLeftPoint.getX() * metersPerPixelX, 2) + Math.pow(topLeftPoint.getY() * metersPerPixelY, 2)); + double distanceFromTopLeftToOrigin = Math.sqrt( + Math.pow(topLeftPoint.getX() * metersPerPixelX, 2) + Math + .pow(topLeftPoint.getY() * metersPerPixelY, 2)); // angle from top left extreme to panel origin - double bearingFromTopLeftToOrigin = Math.toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY())); + double bearingFromTopLeftToOrigin = Math + .toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY())); // the top left extreme Position topLeftPos = new Position(maxLatPoint.getLatitude(), minLonPoint.getLongitude()); - Position originPos = GeoUtility.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin); + Position originPos = GeoUtility + .getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin); // distance from origin corner to bottom right corner of the panel - double distanceFromOriginToBottomRight = Math.sqrt(Math.pow(PANEL_HEIGHT* metersPerPixelY, 2) + Math.pow(PANEL_WIDTH * metersPerPixelX, 2)); - double bearingFromOriginToBottomRight = Math.toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT)); - Position bottomRightPos = GeoUtility.getGeoCoordinate(originPos, bearingFromOriginToBottomRight, distanceFromOriginToBottomRight); + double distanceFromOriginToBottomRight = Math.sqrt( + Math.pow(PANEL_HEIGHT * metersPerPixelY, 2) + Math + .pow(PANEL_WIDTH * metersPerPixelX, 2)); + double bearingFromOriginToBottomRight = Math + .toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT)); + Position bottomRightPos = GeoUtility + .getGeoCoordinate(originPos, bearingFromOriginToBottomRight, + distanceFromOriginToBottomRight); - Boundary boundary = new Boundary(originPos.getLat(), bottomRightPos.getLng(), bottomRightPos.getLat(), originPos.getLng()); + Boundary boundary = new Boundary(originPos.getLat(), bottomRightPos.getLng(), + bottomRightPos.getLat(), originPos.getLng()); CanvasMap canvasMap = new CanvasMap(boundary); mapImage.setImage(canvasMap.getMapImage()); } @@ -196,9 +233,10 @@ public class CanvasController { /** * Adds border marks to the canvas, taken from the XML file * - * NOTE: This is quite confusing as objects are grabbed from the XMLParser such as Mark and CompoundMark which are - * named the same as those in the model package but are, however not the same, so they do not have things such as - * a type and must be derived from the number of marks in a compound mark etc.. + * NOTE: This is quite confusing as objects are grabbed from the XMLParser such as Mark and + * CompoundMark which are named the same as those in the model package but are, however not the + * same, so they do not have things such as a type and must be derived from the number of marks + * in a compound mark etc.. */ private void addRaceBorder() { XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML(); @@ -215,7 +253,7 @@ public class CanvasController { raceBorder.getPoints().setAll(boundaryPoints); } - private void updateGroups(){ + private void updateGroups() { for (BoatGroup boatGroup : boatGroups) { // some raceObjects will have multiple ID's (for instance gate marks) //checking if the current "ID" has any updates associated with it @@ -245,7 +283,7 @@ public class CanvasController { private void updateBoatGroup(BoatGroup boatGroup) { PriorityBlockingQueue movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId()); // giving the movementQueue a 5 packet buffer to account for slightly out of order packets - if (movementQueue.size() > 0){ + if (movementQueue.size() > 0) { try { BoatPositionPacket positionPacket = movementQueue.take(); Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon()); @@ -267,7 +305,7 @@ public class CanvasController { BoatPositionPacket positionPacket = movementQueue.take(); Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon()); markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId); - } catch (InterruptedException e){ + } catch (InterruptedException e) { e.printStackTrace(); } } @@ -282,7 +320,8 @@ public class CanvasController { Group trails = new Group(); Group annotations = new Group(); - ArrayList participants = StreamParser.getXmlObject().getRaceXML().getParticipants(); + ArrayList participants = StreamParser.getXmlObject().getRaceXML() + .getParticipants(); ArrayList participantIDs = new ArrayList<>(); for (Participant p : participants) { participantIDs.add(p.getsourceID()); @@ -315,7 +354,8 @@ public class CanvasController { } else { GateMark gMark = (GateMark) mark; - MarkGroup markGroup = new MarkGroup(gMark, findScaledXY(gMark.getSingleMark1()), findScaledXY(gMark.getSingleMark2())); //should be 2 objects in the list. + MarkGroup markGroup = new MarkGroup(gMark, findScaledXY(gMark.getSingleMark1()), + findScaledXY(gMark.getSingleMark2())); //should be 2 objects in the list. markGroups.add(markGroup); } } @@ -347,6 +387,7 @@ public class CanvasController { public double prefWidth(double height) { return getWidth(); } + @Override public double prefHeight(double width) { return getHeight(); @@ -364,7 +405,8 @@ public class CanvasController { } /** - * Calculates x and y location for every marker that fits it to the canvas the race will be drawn on. + * Calculates x and y location for every marker that fits it to the canvas the race will be + * drawn on. */ private void fitMarksToCanvas() { //Check is called once to avoid unnecessarily change the course limits once the race is running @@ -378,8 +420,9 @@ public class CanvasController { /** - * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker with the leftmost - * marker, rightmost marker, southern most marker and northern most marker respectively. + * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker + * with the leftmost marker, rightmost marker, southern most marker and northern most marker + * respectively. */ private void findMinMaxPoint() { List sortedPoints = new ArrayList<>(); @@ -404,12 +447,13 @@ public class CanvasController { } /** - * Calculates the location of a reference point, this is always the point with minimum latitude, in relation to the - * canvas. + * Calculates the location of a reference point, this is always the point with minimum latitude, + * in relation to the canvas. * - * @param minLonToMaxLon The horizontal distance between the point of minimum longitude to maximum longitude. + * @param minLonToMaxLon The horizontal distance between the point of minimum longitude to + * maximum longitude. */ - private void calculateReferencePointLocation (double minLonToMaxLon) { + private void calculateReferencePointLocation(double minLonToMaxLon) { Mark referencePoint = minLatPoint; double referenceAngle; @@ -438,20 +482,23 @@ public class CanvasController { /** - * Finds the scale factor necessary to fit all race markers within the onscreen map and assigns it to distanceScaleFactor - * Returns the max horizontal distance of the map. + * Finds the scale factor necessary to fit all race markers within the onscreen map and assigns + * it to distanceScaleFactor Returns the max horizontal distance of the map. */ - private double scaleRaceExtremities () { + private double scaleRaceExtremities() { double vertAngle = Math.abs(Mark.calculateHeadingRad(minLatPoint, maxLatPoint)); - double vertDistance = Math.cos(vertAngle) * Mark.calculateDistance(minLatPoint, maxLatPoint); + double vertDistance = + Math.cos(vertAngle) * Mark.calculateDistance(minLatPoint, maxLatPoint); double horiAngle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint); - if (horiAngle <= (Math.PI / 2)) + if (horiAngle <= (Math.PI / 2)) { horiAngle = (Math.PI / 2) - horiAngle; - else + } else { horiAngle = horiAngle - (Math.PI / 2); - double horiDistance = Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint); + } + double horiDistance = + Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint); double vertScale = (CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE)) / vertDistance; @@ -465,8 +512,8 @@ public class CanvasController { return horiDistance; } - private Point2D findScaledXY (Mark unscaled) { - return findScaledXY (unscaled.getLatitude(), unscaled.getLongitude()); + private Point2D findScaledXY(Mark unscaled) { + return findScaledXY(unscaled.getLatitude(), unscaled.getLongitude()); } public Point2D findScaledXY (double unscaledLat, double unscaledLon) { @@ -475,23 +522,35 @@ public class CanvasController { int xAxisLocation = (int) referencePointX; int yAxisLocation = (int) referencePointY; - angleFromReference = Mark.calculateHeadingRad(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, unscaledLon); - distanceFromReference = Mark.calculateDistance(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, unscaledLon); + angleFromReference = Mark + .calculateHeadingRad(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, + unscaledLon); + distanceFromReference = Mark + .calculateDistance(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, + unscaledLon); if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) { - xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + xAxisLocation += (int) Math + .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + yAxisLocation -= (int) Math + .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); } else if (angleFromReference >= 0) { angleFromReference = angleFromReference - Math.PI / 2; - xAxisLocation += (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + xAxisLocation += (int) Math + .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + yAxisLocation += (int) Math + .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); } else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) { angleFromReference = Math.abs(angleFromReference); - xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + xAxisLocation -= (int) Math + .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + yAxisLocation -= (int) Math + .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); } else { angleFromReference = Math.abs(angleFromReference) - Math.PI / 2; - xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + xAxisLocation -= (int) Math + .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + yAxisLocation += (int) Math + .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); } if(horizontalInversion) { xAxisLocation = CANVAS_WIDTH - BUFFER_SIZE - (xAxisLocation - BUFFER_SIZE); @@ -502,7 +561,7 @@ public class CanvasController { /** * Find the number of meters per pixel. */ - private void findMetersPerPixel () { + private void findMetersPerPixel() { Point2D p1, p2; Mark m1, m2; double theta, distance, dx, dy, dHorizontal, dVertical; diff --git a/src/main/java/seng302/controllers/FinishScreenViewController.java b/src/main/java/seng302/controllers/FinishScreenViewController.java new file mode 100644 index 00000000..a2d79f36 --- /dev/null +++ b/src/main/java/seng302/controllers/FinishScreenViewController.java @@ -0,0 +1,97 @@ +package seng302.controllers; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.ResourceBundle; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import seng302.models.Yacht; +import seng302.models.stream.StreamParser; +import seng302.models.stream.XMLParser.RaceXMLObject.Participant; + +public class FinishScreenViewController implements Initializable { + + @FXML + private GridPane finishScreenGridPane; + @FXML + private TableView finishOrderTable; + @FXML + private TableColumn posCol; + @FXML + private TableColumn boatNameCol; + @FXML + private TableColumn shortNameCol; + @FXML + private TableColumn countryCol; + + @Override + public void initialize(URL location, ResourceBundle resources) { + finishScreenGridPane.getStylesheets() + .add(getClass().getResource("/css/master.css").toString()); + finishOrderTable.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + + // set up data for table + ObservableList data = FXCollections.observableArrayList(); + finishOrderTable.setItems(data); + + // setting table col data + posCol.setCellValueFactory( + new PropertyValueFactory<>("position") + ); + boatNameCol.setCellValueFactory( + new PropertyValueFactory<>("boatName") + ); + shortNameCol.setCellValueFactory( + new PropertyValueFactory<>("shortName") + ); + countryCol.setCellValueFactory( + new PropertyValueFactory<>("country") + ); + + // check if the boat is racing + ArrayList participants = StreamParser.getXmlObject().getRaceXML() + .getParticipants(); + ArrayList participantIDs = new ArrayList<>(); + for (Participant p : participants) { + participantIDs.add(p.getsourceID()); + } + + // add data to table + for (Yacht boat : StreamParser.getBoatsPos().values()) { + if (participantIDs.contains(boat.getSourceID())) { + data.add(boat); + } + } + finishOrderTable.refresh(); + } + + private void setContentPane(String jfxUrl) { + try { + // get the main controller anchor pane (FinishView -> MainView) + AnchorPane contentPane = (AnchorPane) finishScreenGridPane.getParent(); + contentPane.getChildren().removeAll(); + contentPane.getChildren().clear(); + contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + contentPane.getChildren() + .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); + } catch (javafx.fxml.LoadException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void switchToStartScreenView() { + setContentPane("/views/StartScreenView.fxml"); + } +} diff --git a/src/main/java/seng302/controllers/LobbyController.java b/src/main/java/seng302/controllers/LobbyController.java new file mode 100644 index 00000000..393ed7d6 --- /dev/null +++ b/src/main/java/seng302/controllers/LobbyController.java @@ -0,0 +1,72 @@ +package seng302.controllers; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import javafx.scene.text.Text; +import seng302.gameServer.GameServerThread; +import seng302.gameServer.GameStages; +import seng302.gameServer.GameState; + +import java.io.IOException; + +/** + * A class describing the actions of the lobby screen + * Created by wmu16 on 10/07/17. + */ +public class LobbyController { + + @FXML + private GridPane lobbyScreen; + @FXML + private Text lobbyIpText; + @FXML + private TableView lobbyTable; + @FXML + private TableColumn ipTableColumn; + @FXML + private TableColumn colourTableColumn; + @FXML + private TableColumn readyTableColumn; + + private GameServerThread gameServerThread; + + private void setContentPane(String jfxUrl) { + try { + AnchorPane contentPane = (AnchorPane) lobbyScreen.getParent(); + contentPane.getChildren().removeAll(); + contentPane.getChildren().clear(); + contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + contentPane.getChildren() + .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); + } catch (javafx.fxml.LoadException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + @FXML + public void leaveLobbyButtonPressed() { + // TODO: 10/07/17 wmu16 - Finish function! + setContentPane("/views/StartScreenView.fxml"); + System.out.println("Leaving lobby!"); + GameState.setCurrentStage(GameStages.CANCELLED); + gameServerThread.terminateGame(); + } + + + @FXML + public void readyButtonPressed() { + GameState.setCurrentStage(GameStages.RACING); + } + + protected void setGameServerThread(GameServerThread gameServerThread) { + this.gameServerThread = gameServerThread; + } +} diff --git a/src/main/java/seng302/controllers/StartScreenController.java b/src/main/java/seng302/controllers/StartScreenController.java index b2238826..9e6f532e 100644 --- a/src/main/java/seng302/controllers/StartScreenController.java +++ b/src/main/java/seng302/controllers/StartScreenController.java @@ -1,188 +1,86 @@ package seng302.controllers; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.ResourceBundle; -import java.util.Timer; -import java.util.TimerTask; -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; -import javafx.fxml.Initializable; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.Parent; +import javafx.scene.control.TextField; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import seng302.models.Yacht; -import seng302.models.stream.StreamParser; -import seng302.models.stream.XMLParser.RaceXMLObject.Participant; +import seng302.gameServer.GameServerThread; +import seng302.gameServer.GameState; +import seng302.models.stream.StreamReceiver; -public class StartScreenController implements Initializable { +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * A Class describing the actions of the start screen controller + * Created by wmu16 on 10/07/17. + */ +public class StartScreenController { @FXML - private GridPane gridPane; + private TextField ipTextField; @FXML - private Label timeTillLive; - @FXML - private Button streamButton; - @FXML - private Button switchToRaceViewButton; - @FXML - private TableView teamList; - @FXML - private TableColumn boatNameCol; - @FXML - private TableColumn shortNameCol; - @FXML - private TableColumn countryCol; - @FXML - private TableColumn posCol; - @FXML - private Label realTime; + private GridPane startScreen2; - private boolean switchedToRaceView = false; - - private void setContentPane(String jfxUrl) { + /** + * Loads the fxml content into the parent pane + * @param jfxUrl + * @return the controller of the fxml + */ + private Object setContentPane(String jfxUrl) { try { - // get the main controller anchor pane (MainView.fxml) - AnchorPane contentPane = (AnchorPane) gridPane.getParent(); + AnchorPane contentPane = (AnchorPane) startScreen2.getParent(); contentPane.getChildren().removeAll(); contentPane.getChildren().clear(); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - contentPane.getChildren() - .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl)); + contentPane.getChildren().addAll((Pane) fxmlLoader.load()); + + return fxmlLoader.getController(); } catch (javafx.fxml.LoadException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } + return null; } - @Override - public void initialize(URL location, ResourceBundle resources) { - gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - } /** - * Running a timer to update the livestream status on welcome screen. Update interval is 1 - * second. + * ATTEMPTS TO: + * Sets up a new game state with your IP address as designated as the host. + * Starts a thread to listen for incoming connections + * Switches to the lobby screen */ - public void startStream() { - if (StreamParser.isStreamStatus()) { - streamButton.setVisible(false); - realTime.setVisible(true); - timeTillLive.setVisible(true); - timeTillLive.setTextFill(Color.GREEN); - timeTillLive.setText("Connecting..."); - Timer timer = new Timer(); - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - Platform.runLater(() -> { - if (StreamParser.isRaceStarted()) { - if (!switchedToRaceView) { - switchToRaceView(); - } - timer.cancel(); - } - if (StreamParser.isRaceFinished()) { - realTime.setText(StreamParser.getCurrentTimeString()); - timeTillLive.setTextFill(Color.RED); - timeTillLive.setText("Race finished! Waiting for new race..."); - switchToRaceViewButton.setDisable(true); - } else if (StreamParser.getTimeSinceStart() > 0) { - realTime.setText(StreamParser.getCurrentTimeString()); - updateTeamList(); - timeTillLive.setTextFill(Color.RED); - switchToRaceViewButton.setDisable(false); - String timerMinute = Long - .toString(StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long - .toString(StreamParser.getTimeSinceStart() % 60); - if (timerSecond.length() == 1) { - timerSecond = "0" + timerSecond; - } - String timerString = "-" + timerMinute + ":" + timerSecond; - timeTillLive.setText(timerString); - } else { - realTime.setText(StreamParser.getCurrentTimeString()); - updateTeamList(); - timeTillLive.setTextFill(Color.BLACK); - switchToRaceViewButton.setDisable(false); - String timerMinute = Long - .toString(-1 * StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long - .toString(-1 * StreamParser.getTimeSinceStart() % 60); - if (timerSecond.length() == 1) { - timerSecond = "0" + timerSecond; - } - String timerString = timerMinute + ":" + timerSecond; - timeTillLive.setText(timerString); - } - }); - } - }, 0, 1000); - } else { - timeTillLive.setText("Stream not available."); - timeTillLive.setTextFill(Color.RED); + @FXML + public void hostButtonPressed() { + try { + String ipAddress = InetAddress.getLocalHost().getHostAddress(); + new GameState(ipAddress); + GameServerThread gameServerThread = new GameServerThread("Game Server"); + System.out.println("Server thread started"); + + // get the lobby controller so that we can pass the game server thread to it + LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml"); + lobbyController.setGameServerThread(gameServerThread); + + } catch (UnknownHostException e) { + System.err.println("COULD NOT FIND YOUR IP ADDRESS!"); + e.printStackTrace(); } + } - public void switchToRaceView() { - StreamParser.boatLocations.clear(); - switchedToRaceView = true; - setContentPane("/views/RaceView.fxml"); - } - private void updateTeamList() { - ObservableList data = FXCollections.observableArrayList(); - - teamList.setItems(data); - - boatNameCol.setCellValueFactory( - new PropertyValueFactory<>("boatName") - ); - shortNameCol.setCellValueFactory( - new PropertyValueFactory<>("shortName") - ); - countryCol.setCellValueFactory( - new PropertyValueFactory<>("country") - ); - posCol.setCellValueFactory( - new PropertyValueFactory<>("position") - ); - - // check if the boat is racing - ArrayList participants = StreamParser.getXmlObject().getRaceXML() - .getParticipants(); - ArrayList participantIDs = new ArrayList<>(); - for (Participant p : participants) { - participantIDs.add(p.getsourceID()); - } - - // add boats to the start screen list - if (StreamParser.isRaceStarted()) { // if race is started, use StreamParser.getBoatsPos() - for (Yacht boat : StreamParser.getBoatsPos().values()) { - if (participantIDs.contains(boat.getSourceID())) { - data.add(boat); - } - } - } else { // else use StreamParser.getBoats() - for (Yacht boat : StreamParser.getBoats().values()) { - if (participantIDs.contains(boat.getSourceID())) { - data.add(boat); - } - } - } - teamList.refresh(); + @FXML + public void connectButtonPressed() { + // TODO: 10/07/17 wmu16 - Finish function + String ipAddress = ipTextField.getText().trim(); + StreamReceiver sr = new StreamReceiver(ipAddress, GameServerThread.PORT_NUMBER, "HostStream"); + sr.start(); } } diff --git a/src/main/java/seng302/gameServer/ClientConnectionDelegate.java b/src/main/java/seng302/gameServer/ClientConnectionDelegate.java new file mode 100644 index 00000000..35d839bc --- /dev/null +++ b/src/main/java/seng302/gameServer/ClientConnectionDelegate.java @@ -0,0 +1,17 @@ +package seng302.gameServer; + +import seng302.models.Player; + +public interface ClientConnectionDelegate { + /** + * A player has connected to the server + * @param player The player that has connected + */ + void clientConnected(Player player); + + /** + * A player has disconnected from the server + * @param player The player that has disconnected + */ + void clientDisconnected(Player player); +} diff --git a/src/main/java/seng302/gameServer/GameServerThread.java b/src/main/java/seng302/gameServer/GameServerThread.java new file mode 100644 index 00000000..91d17cc2 --- /dev/null +++ b/src/main/java/seng302/gameServer/GameServerThread.java @@ -0,0 +1,371 @@ +package seng302.gameServer; + +import seng302.models.Player; +import seng302.models.Yacht; +import seng302.server.messages.*; +import seng302.server.simulator.Boat; +import seng302.server.simulator.Simulator; + +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.SocketOption; +import java.net.SocketOptions; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.*; + +public class GameServerThread implements Runnable, Observer, ClientConnectionDelegate{ + + private static final Integer MAX_NUM_PLAYERS = 10; + public static final int PORT_NUMBER = 4950; + + private Boolean hosting = true; + + private ServerSocketChannel server; + private long startTime; + private short seqNum; + + private final int RACE_STATUS_PERIOD = 1000/2; + private final int RACE_START_STATUS_PERIOD = 1000; + private final int BOAT_LOCATION_PERIOD = 1000/5; + private final int TIME_TILL_RACE_START = 20*1000; + private static final int LOG_LEVEL = 1; + + public GameServerThread(String threadName){ + Thread runner = new Thread(this, threadName); + runner.setDaemon(true); + seqNum = 0; + + runner.start(); + } + + static void serverLog(String message, int logLevel){ + if(logLevel <= LOG_LEVEL){ + System.out.println("[SERVER] " + message); + } + } + + /** + * Creates and returns an XML Message from the file specified + * @param fileName The source XML file + * @param type The XML Message type + * @return The XML Message + */ + private Message getXmlMessage(String fileName, XMLMessageSubType type){ + String fileContents = null; + + try { + InputStream thisStream = this.getClass().getResourceAsStream(fileName); + fileContents = new String(org.apache.commons.io.IOUtils.toByteArray(thisStream)); + } catch (IOException e) { + e.printStackTrace(); + } catch (NullPointerException e){ + return null; + } + + if (fileContents != null){ + return new XMLMessage(fileContents, type, seqNum); + } + + return null; + } + + /** + * @return Get a race status message for the current race + */ + private Message getRaceStatusMessage(){ + + List boatSubMessages = new ArrayList<>(); + BoatStatus boatStatus; + RaceStatus raceStatus; + boolean thereAreBoatsNotFinished = false; + + for (Player player : GameState.getPlayers()){ + Yacht y = player.getYacht(); + + if (GameState.getCurrentStage() == GameStages.PRE_RACE){ + boatStatus = BoatStatus.PRESTART; + thereAreBoatsNotFinished = true; + } + else if(false){ //@TODO if boat has finished + boatStatus = BoatStatus.FINISHED; + } + else{ + boatStatus = BoatStatus.PRESTART; + thereAreBoatsNotFinished = true; + } + + BoatSubMessage m = new BoatSubMessage(y.getSourceID(), boatStatus, y.getLastMarkRounded().getId(), 0, 0, 1234l, 1234l); + boatSubMessages.add(m); + } + + if (thereAreBoatsNotFinished){ + if (GameState.getCurrentStage() == GameStages.RACING){ + raceStatus = RaceStatus.STARTED; + } + else{ + long currentTime = System.currentTimeMillis(); + long timeDifference = startTime - currentTime; + + if (timeDifference > 60*3){ + raceStatus = RaceStatus.PRESTART; + } + else if (timeDifference > 60){ + raceStatus = RaceStatus.WARNING; + } + else{ + raceStatus = RaceStatus.PREPARATORY; + } + } + } + else{ + raceStatus = RaceStatus.TERMINATED; + } + + return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.SOUTH, + 100, GameState.getPlayers().size(), RaceType.MATCH_RACE, 1, boatSubMessages); + } + + /** + * Start sending race start status messages until race starts + */ + private void startSendingRaceStartStatusMessages(){ + Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + Message raceStartStatusMessage = new RaceStartStatusMessage(seqNum, startTime , 1, + RaceStartNotificationType.SET_RACE_START_TIME); + try { + if (startTime < System.currentTimeMillis() && GameState.getCurrentStage() != GameStages.RACING){ + } + else{ + broadcast(raceStartStatusMessage); + } + + } catch (IOException e) { + e.printStackTrace(); + } + } + }, 0, RACE_START_STATUS_PERIOD); + } + + /** + * Start sending race start status messages until race starts + */ + private void startSendingRaceStatusMessages(){ + + Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + Message raceStatusMessage = getRaceStatusMessage(); + try { + broadcast(raceStatusMessage); + } catch (IOException e) { + e.printStackTrace(); + } + } + }, 0, RACE_STATUS_PERIOD); + } + + /** + * Sends the race, boat, and regatta XML files to the client + */ + private void sendXml(){ + try{ + Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE); + Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT); + Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA); + + if (raceData != null){ + broadcast(raceData); + } + if (boatData != null){ + broadcast(boatData); + } + if (regatta != null){ + broadcast(regatta); + } + } catch (IOException e) { + serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); + } + } + + /** + * Send the post-start race course information + */ + private void sendPostStartCourseXml(){ + Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + try { + Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE); + if (raceData != null) { + broadcast(raceData); + } + }catch (IOException e) { + serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); + } + } + },1000); + //Delays the new course xml data for 25 seconds so the boats are able to pass the starting line + } + + public void run() { + ServerListenThread serverListenThread; + HeartbeatThread heartbeatThread; + Boolean serverIsSendingMessages = false; + + try{ + server = ServerSocketChannel.open(); + server.socket().bind(new InetSocketAddress("localhost", PORT_NUMBER)); + + serverListenThread = new ServerListenThread(server, this); + heartbeatThread = new HeartbeatThread(this); + + heartbeatThread.start(); + serverListenThread.start(); + } + catch (IOException e){ + serverLog("Failed to bind socket: " + e.getMessage(), 0); + } + + while (hosting) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (GameState.getCurrentStage() == GameStages.RACING && !serverIsSendingMessages) { + serverLog("Race Started", 0); + + sendXml(); + startSendingRaceStartStatusMessages(); + //startSendingRaceStatusMessages(); + sendPostStartCourseXml(); + serverIsSendingMessages = true; + } + + else if (GameState.getCurrentStage() == GameStages.FINISHED) { + serverLog("Race Finished", 0); + } + + startTime = System.currentTimeMillis() + TIME_TILL_RACE_START; + } + } + +// /** +// * Start sending static boat position updates when race has finished +// */ +// private void startSendingRaceFinishedBoatPositions(){ +// Timer t = new Timer(); +// t.schedule(new TimerTask() { +// @Override +// public void run() { +// try { +// for (Boat b : raceSimulator.getBoats()){ +// Message m = new BoatLocationMessage(b.getSourceID(), seqNum, b.getLat(), +// b.getLng(), b.getLastPassedCorner().getBearingToNextCorner(), +// ((long) 0)); +// +// server.send(m); +// } +// +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +// }, 0, BOAT_LOCATION_PERIOD); +// } + + /** + * A client has tried to connect to the server + * @param player The player that connected + */ + @Override + public void clientConnected(Player player) { + if (GameState.getPlayers().size() < MAX_NUM_PLAYERS && GameState.getCurrentStage() == GameStages.LOBBYING) { + serverLog("Player Connected", 0); + GameState.addPlayer(player); + sendXml(); + } + } + + /** + * A player has left the game, remove the player from the GameState + * @param player The player that left + */ + @Override + public void clientDisconnected(Player player) { + serverLog("Player disconnected", 0); + GameState.removePlayer(player); + sendXml(); + } + + void unicast(Message message, SocketChannel client) throws IOException { + message.send(client); + } + + void broadcast(Message message) throws IOException{ + for(Player player : GameState.getPlayers()) { + //System.out.println("Sending message seqNo[" + seqNum + "] to Player: " + player.toString()); + message.send(player.getSocketChannel()); + } + seqNum++; + } + + /** + * Send a boat location message when they are updated by the simulator + * @param o . + * @param arg . + */ + @Override + @SuppressWarnings("unchecked") + public void update(Observable o, Object arg) { + /* Only send if server started + // TODO: I don't understand why i need to check server is null or not ... confused - haoming 2/5/17 + if(server == null || !server.isStarted()){ + return; + } + + int numOfBoatsFinished = 0; + for (Boat boat : (List) arg){ + try { + if (boat.isFinished()) { + numOfBoatsFinished ++; + if (!boatsFinished.get(boat.getSourceID())) { + boatsFinished.put(boat.getSourceID(), true); + } + } + Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(), + boat.getLng(), boat.getLastPassedCorner().getBearingToNextCorner(), + ((long) boat.getSpeed())); + broadcast(m); + } catch (IOException e) { + serverLog("Couldn't send a boat status message", 3); + return; + } + catch (NullPointerException e){ + e.printStackTrace(); + }*/ + } + +// if (numOfBoatsFinished == ((List) arg).size()) { +// startSendingRaceFinishedBoatPositions(); +// } + + //} + + public void terminateGame() { + try { + //TODO: for now, I just close the socket, but i think we should terminate the whole thread instead. -hyi25 13 July + server.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/gameServer/GameStages.java b/src/main/java/seng302/gameServer/GameStages.java new file mode 100644 index 00000000..ef436f64 --- /dev/null +++ b/src/main/java/seng302/gameServer/GameStages.java @@ -0,0 +1,24 @@ +package seng302.gameServer; + +/** + * An enum describing the states of the game + * Created by wmu16 on 11/07/17. + */ +public enum GameStages { + + LOBBYING(0), + PRE_RACE(1), + RACING(2), + FINISHED(3), + CANCELLED(4); + + private long code; + + GameStages(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java new file mode 100644 index 00000000..d36968fc --- /dev/null +++ b/src/main/java/seng302/gameServer/GameState.java @@ -0,0 +1,65 @@ +package seng302.gameServer; + +import seng302.models.Player; + +import java.util.ArrayList; + +/** + * A Static class to hold information about the current state of the game (model) + * Created by wmu16 on 10/07/17. + */ +public class GameState { + + private static String hostIpAddress; + private static ArrayList players; + private static Boolean isRaceStarted; + private static GameStages currentStage; + + public GameState(String hostIpAddress) { + GameState.hostIpAddress = hostIpAddress; + players = new ArrayList<>(); + currentStage = GameStages.LOBBYING; + isRaceStarted = false; + } + + public static String getHostIpAddress() { + return hostIpAddress; + } + + public static ArrayList getPlayers() { + return players; + } + + public static void addPlayer(Player player) { + players.add(player); + } + + public static void removePlayer(Player player) { + players.remove(player); + } + + public static Boolean getIsRaceStarted() { + return isRaceStarted; + } + + public static GameStages getCurrentStage() { + return currentStage; + } + + public static void setCurrentStage(GameStages currentStage) { + GameState.currentStage = currentStage; + } + + /** + * This iterates through all players and updates each players info to its new state based on its current data + */ + private void update(){ + for(Player player : players) { + // TODO: 10/07/17 wmu16 - Update all player info + } + } + + + + +} diff --git a/src/main/java/seng302/gameServer/HeartbeatThread.java b/src/main/java/seng302/gameServer/HeartbeatThread.java new file mode 100644 index 00000000..e10a85f5 --- /dev/null +++ b/src/main/java/seng302/gameServer/HeartbeatThread.java @@ -0,0 +1,81 @@ +package seng302.gameServer; + +import seng302.models.Player; +import seng302.server.messages.Heartbeat; +import seng302.server.messages.Message; + +import java.io.IOException; +import java.util.*; + +/** + * Send Heartbeat messages to connected player at a specified interval + * Will call .clientDisconnected on the delegate when a heartbeat message + * cannot be sent to a player + */ +public class HeartbeatThread extends Thread{ + private final int HEARTBEAT_PERIOD = 5000; + private ClientConnectionDelegate delegate; + private Integer seqNum; + private Stack disconnectedPlayers; + + HeartbeatThread(ClientConnectionDelegate delegate){ + this.delegate = delegate; + seqNum = 0; + disconnectedPlayers = new Stack<>(); + } + + /** + * A player has lost connection to the server + * The player is added to a stack so that the delegate + * can be notified + * + * @param player The player that has disconnected + */ + private void playerLostConnection(Player player){ + disconnectedPlayers.push(player); + } + + /** + * Sends a heartbeat message to each connected player + * The delegate is notified if a player has disconnected + */ + private void sendHeartbeatToAllPlayers(){ + Message heartbeat = new Heartbeat(seqNum); + + for (Player player : GameState.getPlayers()){ + if (!player.getSocketChannel().isConnected()){ + playerLostConnection(player); + } + + try { + heartbeat.send(player.getSocketChannel()); + } catch (IOException e) { + playerLostConnection(player); + } + } + + updateDelegate(); + seqNum++; + } + + /** + * Notifies the delegate about + * each disconnected player + */ + private void updateDelegate() { + while (!disconnectedPlayers.empty()){ + delegate.clientDisconnected(disconnectedPlayers.pop()); + } + } + + public void run(){ + Timer t = new Timer(); + + t.schedule(new TimerTask() { + @Override + public void run() { + sendHeartbeatToAllPlayers(); + } + }, 0, HEARTBEAT_PERIOD); + } +} diff --git a/src/main/java/seng302/gameServer/ServerListenThread.java b/src/main/java/seng302/gameServer/ServerListenThread.java new file mode 100644 index 00000000..cfcbdcaa --- /dev/null +++ b/src/main/java/seng302/gameServer/ServerListenThread.java @@ -0,0 +1,42 @@ +package seng302.gameServer; + +import seng302.models.Player; + +import java.io.IOException; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +/** + * A class for a thread to listen to connections + * Created by wmu16 on 11/07/17. + */ +public class ServerListenThread extends Thread{ + private ServerSocketChannel socketChannel; + private ClientConnectionDelegate delegate; + + ServerListenThread(ServerSocketChannel socketChannel, ClientConnectionDelegate delegate){ + this.socketChannel = socketChannel; + this.delegate = delegate; + } + + /** + * Listens for a connection and upon finding one, creates a Player object and adds it to the universal GameState + */ + private void acceptConnection() { + try { + SocketChannel thisClient = socketChannel.accept(); + if (thisClient.socket() != null){ + Player thisPlayer = new Player(thisClient); + delegate.clientConnected(thisPlayer); + } + } catch (IOException e) { + e.getMessage(); + } + } + + public void run(){ + while (true){ + acceptConnection(); + } + } +} diff --git a/src/main/java/seng302/gameServerWithThreading/ServerThread.java b/src/main/java/seng302/gameServerWithThreading/ServerThread.java new file mode 100644 index 00000000..cca96035 --- /dev/null +++ b/src/main/java/seng302/gameServerWithThreading/ServerThread.java @@ -0,0 +1,105 @@ +package seng302.gameServerWithThreading; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * 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 ServerThread extends Thread { + + private static final Integer MAX_ID_ATTEMPTS = 10; + + private InputStream is; + private OutputStream os; + private Socket socket; + + private Boolean userIdentified = false; + private Boolean connected = true; + private Boolean updateClient = true; + + public ServerThread(Socket socket) { + this.socket = socket; + } + + public void run() { + try { + is = socket.getInputStream(); + os = socket.getOutputStream(); + } catch (IOException e) { + System.out.println("IO error in server thread upon grabbing streams"); + } + + threeWayHandshake(); + + // TODO: 13/07/17 wmu16 - Some way of knowing if the client is still connected. perhaps when we read disconnect message switch this bool? + while (connected) { + + //Perform a read and update game state + try { + Integer userInput = is.read(); + } catch (IOException e) { + System.out.println("IO error in server thread upon reading input stream"); + } + + + //Perform a write if it is time to as delegated by the ServerThreadHandler + 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; + } + } + + closeSocket(); + + } + + public void updateClient() { + updateClient = true; + } + + + /** + * Tries to confirm the connection just accepted. + * Sends ID, expects that ID echoed for confirmation, + * if so, sends a confirmation packet back to that connection + * Creates a player instance with that ID and this thread and adds it to the GameState + * If not, close the socket and end the threads execution + */ + private void threeWayHandshake() { + // TODO: 13/07/17 Finish using AC35 +// Integer playerID = GameState.getUniquePlayerID(); +// Integer confirmationID = null; +// Integer identificationAttempt = 0 +// while (!userIdentified) { +// os.write(playerID); //Send out new ID looking for echo +// confirmationID = is.read(); +// if (playerID == idConfirmation) { //ID is echoed back. Connection is a client +// os.write( some determined confirmation message ); //Confirm to client +// GameState.addPlayer(new Player(playerID, this)); //Create a player in game state for client +// userIdentified = true; +// } else if (identificationAttempt > MAX_ID_ATTEMPTS) { //No response. not a client. tidy up and go home. +// closeSocket(); +// return; +// } +// identificationAttempt++; +// } + } + + public void closeSocket() { + try { + socket.close(); + } catch (IOException e) { + System.out.println("IO error in server thread upon trying to close socket"); + } + } +} diff --git a/src/main/java/seng302/gameServerWithThreading/ServerThreadHandler.java b/src/main/java/seng302/gameServerWithThreading/ServerThreadHandler.java new file mode 100644 index 00000000..dffedd60 --- /dev/null +++ b/src/main/java/seng302/gameServerWithThreading/ServerThreadHandler.java @@ -0,0 +1,67 @@ +package seng302.gameServerWithThreading; + +import seng302.gameServer.GameStages; +import seng302.gameServer.GameState; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; + +/** + * A class describing the overall server, which creates and collects server threads for each client + * Created by wmu16 on 13/07/17. + */ +public class ServerThreadHandler extends Thread { + + private static final int PORT = 4950; + private static final Integer MAX_NUM_PLAYERS = 10; + + private ServerSocket serverSocket = null; + private Socket socket; + private ArrayList serverThreads = new ArrayList<>(); + + public ServerThreadHandler() { + try { + serverSocket = new ServerSocket(PORT); + } catch (IOException e) { + System.out.println("IO error in server thread handler upon trying to make new server socket"); + } + } + + + public void run() { + //You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app. + while (!isInterrupted()) { + try { + Thread.sleep(1000 / 60); //60 times per second we should calculate the game state + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState.getPlayers().size() < MAX_NUM_PLAYERS) { + try { + socket = serverSocket.accept(); + } catch (IOException e) { + System.out.println("IO error in server thread handler upon trying to accept connection"); + } + ServerThread thread = new ServerThread(socket); + serverThreads.add(thread); + thread.start(); + } + + updateClients(); + } + + try { + serverSocket.close(); + } catch (IOException e) { + System.out.println("IO error in server thread handler upon closing socket"); + } + } + + public void updateClients() { + for (ServerThread serverThread : serverThreads) { + serverThread.updateClient(); + } + } +} diff --git a/src/main/java/seng302/models/Player.java b/src/main/java/seng302/models/Player.java new file mode 100644 index 00000000..fb062d0d --- /dev/null +++ b/src/main/java/seng302/models/Player.java @@ -0,0 +1,73 @@ +package seng302.models; + +import javafx.scene.paint.Color; + +import java.io.IOException; +import java.nio.channels.SocketChannel; + +/** + * A Class defining a player and their respective details in the game as held by the model + * Created by wmu16 on 10/07/17. + */ +public class Player { + + private SocketChannel socketChannel; + private Yacht yacht; + private Integer lastMarkPassed; + + + public Player(SocketChannel socketChannel) { + this.socketChannel = socketChannel; + } + + public SocketChannel getSocketChannel() { + return socketChannel; + } + + public Integer getLastMarkPassed() { + return lastMarkPassed; + } + + public void setLastMarkPassed(Integer lastMarkPassed) { + this.lastMarkPassed = lastMarkPassed; + } + + public Yacht getYacht() { + return yacht; + } + + @Override + public String toString() { + String playerAddress = null; + + if (socketChannel == null){ + return "Disconnected Player"; + } + + try { + playerAddress = socketChannel.getRemoteAddress().toString(); + } catch (IOException e) { + e.printStackTrace(); + } + + return playerAddress; + } + + @Override + public boolean equals(Object obj) { + if (obj == null){ + return false; + } + + if (!(obj instanceof Player)){ + return false; + } + + return ((Player) obj).socketChannel.equals(socketChannel); + } + + @Override + public int hashCode(){ + return socketChannel.hashCode(); + } +} diff --git a/src/main/java/seng302/models/PolarTable.java b/src/main/java/seng302/models/PolarTable.java index 168d2291..b40b54dd 100644 --- a/src/main/java/seng302/models/PolarTable.java +++ b/src/main/java/seng302/models/PolarTable.java @@ -24,9 +24,8 @@ public final class PolarTable { * Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed. * These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap * as a value - * @param file containing the polar csv information */ - public static void parsePolarFile(String file) { + public static void parsePolarFile(InputStream polarFile) { polarTable = new HashMap<>(); upwindOptimal = new HashMap<>(); downwindOptimal = new HashMap<>(); @@ -34,7 +33,7 @@ public final class PolarTable { String line; Boolean isHeaderLine = true; - try (BufferedReader br = new BufferedReader(new FileReader(file))) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) { while ((line = br.readLine()) != null) { String[] thisLine = line.split(","); @@ -69,6 +68,8 @@ public final class PolarTable { } catch (IOException e) { e.printStackTrace(); } + + } diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/models/Yacht.java index c11c7407..7ea7e97e 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/models/Yacht.java @@ -24,6 +24,10 @@ public class Yacht { private String shortName; private String boatName; private String country; + + // Situational data + + // Boat status private Integer boatStatus; private Integer legNumber; @@ -31,6 +35,9 @@ public class Yacht { private Integer penaltiesServed; private Long estimateTimeAtFinish; private String position; + private Double lat; + private Double lon; + private Float heading; private double velocity; private Long timeTillNext; private Long markRoundTime; @@ -191,17 +198,41 @@ public class Yacht { this.lastMarkRounded = lastMarkRounded; } + public void setNextMark(Mark nextMark) { + this.nextMark = nextMark; + } + + public Mark getNextMark(){ + return nextMark; + } + + public Double getLat() { + return lat; + } + + public void setLat(Double lat) { + this.lat = lat; + } + + public Double getLon() { + return lon; + } + + public void setLon(Double lon) { + this.lon = lon; + } + + public Float getHeading() { + return heading; + } + + public void setHeading(Float heading) { + this.heading = heading; + } + @Override public String toString() { return boatName; } - public void setNextMark(Mark nextMark) { - this.nextMark = nextMark; - } - - public Mark getNextMark(){ - return nextMark; - } - } diff --git a/src/main/java/seng302/server/messages/Header.java b/src/main/java/seng302/server/messages/Header.java index c4dc6251..2b520611 100644 --- a/src/main/java/seng302/server/messages/Header.java +++ b/src/main/java/seng302/server/messages/Header.java @@ -43,10 +43,21 @@ public class Header { buff.position(buffPos); } + /** + * Reset the buffer + */ + public void reset(){ + buffPos = 0; + buff.clear(); + buff.position(buffPos); + } + /** * @return a ByteBuffer containing the message header */ public ByteBuffer getByteBuffer(){ + reset(); + putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1); putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2); diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java index e1bf2b53..7ff4fbef 100644 --- a/src/main/java/seng302/server/messages/Message.java +++ b/src/main/java/seng302/server/messages/Message.java @@ -45,6 +45,7 @@ public abstract class Message { buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE); buffer.order(ByteOrder.LITTLE_ENDIAN); bufferPosition = 0; + buffer.position(bufferPosition); } /** diff --git a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java index 368a18fd..9aa886ee 100644 --- a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java @@ -54,6 +54,15 @@ public class RaceStartStatusMessage extends Message { writeCRC(); rewind(); - outputStream.write(getBuffer()); + if (outputStream == null){ + return; + } + + try{ + outputStream.write(getBuffer()); + } + catch (IOException e){ + return; + } } } diff --git a/src/main/resources/css/master.css b/src/main/resources/css/master.css index 42fc44c7..7620d831 100644 --- a/src/main/resources/css/master.css +++ b/src/main/resources/css/master.css @@ -168,7 +168,7 @@ Remove scroll bars .ui-table *.scroll-bar:vertical *.increment-arrow, .ui-table *.scroll-bar:vertical *.decrement-arrow { - -fx-background-color: null; + -fx-background-color: #0e6d6c; -fx-background-radius: 0; -fx-background-insets: 0; -fx-padding: 0; @@ -177,7 +177,7 @@ Remove scroll bars .ui-table *.scroll-bar:vertical *.increment-button, .ui-table *.scroll-bar:vertical *.decrement-button { - -fx-background-color: null; + -fx-background-color: #0e6d6c; -fx-background-radius: 0; -fx-background-insets: 0; -fx-padding: 0; diff --git a/src/main/resources/views/FinishScreenView.fxml b/src/main/resources/views/FinishScreenView.fxml new file mode 100644 index 00000000..736c8b74 --- /dev/null +++ b/src/main/resources/views/FinishScreenView.fxml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +