diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index dbf93693..10001869 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -15,10 +14,6 @@ import java.util.TimerTask; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.zip.CRC32; import java.util.zip.Checksum; -import javafx.application.Platform; -import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.ButtonType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import seng302.gameServer.messages.BoatAction; @@ -46,16 +41,20 @@ public class ClientToServerThread implements Runnable { void newPacket(); } + @FunctionalInterface + public interface DisconnectedFromHostListener { + void notifYDisconnection (String message); + } + private class ByteReadException extends Exception { private ByteReadException(String message) { super(message); } } - private static final int LOG_LEVEL = 1; - private Queue streamPackets = new ConcurrentLinkedQueue<>(); private List listeners = new ArrayList<>(); + private List disconnectionListeners = new ArrayList<>(); private Thread thread; private Socket socket; @@ -72,7 +71,6 @@ public class ClientToServerThread implements Runnable { private int clientId = -1; -// private Boolean updateClient = true; private ByteArrayOutputStream crcBuffer; private boolean socketOpen = true; @@ -99,20 +97,6 @@ public class ClientToServerThread implements Runnable { thread.start(); } - /** - * Prints out log messages and the time happened. - * Only perform task if log level is below LOG_LEVEL variable. - * - * @param message a string of message to be printed out - * @param logLevel an int for log level - */ - static void clientLog(String message, int logLevel) { - if (logLevel <= LOG_LEVEL) { - System.out.println( - "[CLIENT " + LocalDateTime.now().toLocalTime().toString() + "] " + message); - } - } - /** * Perform the thread loop. It exits the loop if ClientState connected to host * variable is false. @@ -121,7 +105,7 @@ public class ClientToServerThread implements Runnable { int sync1; int sync2; // TODO: 14/07/17 wmu16 - Work out how to fix this while loop - while(socketOpen) { + while(!socket.isClosed() && socket.isConnected() && socketOpen) { try { crcBuffer = new ByteArrayOutputStream(); sync1 = readByte(); @@ -153,26 +137,25 @@ public class ClientToServerThread implements Runnable { } } } else { - clientLog("Packet has been dropped", 1); + logger.warn("Packet has been dropped", 1); } } } catch (ByteReadException e) { - e.printStackTrace(); + logger.warn("Byte read exception on ClientToServerThread", 1); closeSocket(); - Platform.runLater(() -> { - Alert alert = new Alert(AlertType.ERROR); - alert.setHeaderText("Host has disconnected"); - alert.setContentText("Cannot find Server"); - alert.showAndWait(); - }); - clientLog(e.getMessage(), 1); - return; + notifyDisconnectListeners("Connection to server was interrupted"); } } + logger.warn("Closed connection to server", 1); closeSocket(); - clientLog("Closed connection to Server", 0); + notifyDisconnectListeners("Connection to server was terminated"); } + private void notifyDisconnectListeners (String message) { + for (DisconnectedFromHostListener listener : disconnectionListeners) { + listener.notifYDisconnection(message); + } + } /** * Sends a request to the server asking for a source ID @@ -184,7 +167,8 @@ public class ClientToServerThread implements Runnable { os.write(requestMessage.getBuffer()); } catch (IOException e) { logger.error("Could not send registration request. Exiting"); - System.exit(1); + closeSocket(); + notifyDisconnectListeners("Failed to register with server"); } } @@ -200,7 +184,6 @@ public class ClientToServerThread implements Runnable { if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){ clientId = sourceId; - return; } @@ -214,11 +197,8 @@ public class ClientToServerThread implements Runnable { else{ alertErrorText = "Could not connect to server"; } - - Platform.runLater(() -> { - new Alert(AlertType.ERROR, alertErrorText, ButtonType.OK).showAndWait(); - System.exit(1); - }); + closeSocket(); + notifyDisconnectListeners(alertErrorText); } /** @@ -228,7 +208,7 @@ public class ClientToServerThread implements Runnable { * - MAINTAIN_HEADING = DOWNWIND and UPWIND packets stop being sent. * @param actionType The boat action that will dictate packets sent. */ - public void sendBoatAction(BoatAction actionType) { + public void sendBoatActionMessage(BoatAction actionType) { switch (actionType) { case MAINTAIN_HEADING: if (upwindTimerFlag) { @@ -249,7 +229,7 @@ public class ClientToServerThread implements Runnable { new TimerTask() { @Override public void run() { - sendBoatAction(new BoatActionMessage(BoatAction.DOWNWIND)); + sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND)); } }, 0, PACKET_SENDING_INTERVAL_MS ); @@ -262,14 +242,14 @@ public class ClientToServerThread implements Runnable { new TimerTask() { @Override public void run() { - sendBoatAction(new BoatActionMessage(BoatAction.UPWIND)); + sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND)); } }, 0, PACKET_SENDING_INTERVAL_MS ); } break; default: - sendBoatAction(new BoatActionMessage(actionType)); + sendBoatActionMessage(new BoatActionMessage(actionType)); break; } } @@ -287,12 +267,14 @@ public class ClientToServerThread implements Runnable { * Sends a boat action of the given message type. * @param message The given message type. */ - private void sendBoatAction(BoatActionMessage message) { + private void sendBoatActionMessage(BoatActionMessage message) { if (clientId != -1) { try { os.write(message.getBuffer()); } catch (IOException e) { - clientLog("Could not write to server", 1); + logger.warn("IOException on attempting to sendBoatActionMessage from Client"); + closeSocket(); + notifyDisconnectListeners("Cannot communicate with server"); } } } @@ -301,7 +283,7 @@ public class ClientToServerThread implements Runnable { try { socket.close(); } catch (IOException e) { - clientLog("Failed to close the socket", 1); + logger.warn("IOException on attempting to close ClientToServerSocket"); } } @@ -321,13 +303,23 @@ public class ClientToServerThread implements Runnable { listeners.remove(streamListener); } + public void addDisconnectionListener (DisconnectedFromHostListener listener) { + disconnectionListeners.add(listener); + } + + public void removeDisconnectionListener (DisconnectedFromHostListener listener) { + disconnectionListeners.remove(listener); + } + private int readByte() throws ByteReadException { int currentByte = -1; try { currentByte = is.read(); crcBuffer.write(currentByte); } catch (IOException e) { - clientLog("Read byte failed", 1); + logger.warn("IOException on readByte Client side", 1); + closeSocket(); + notifyDisconnectListeners("Cannot read from server."); } if (currentByte == -1) { throw new ByteReadException("InputStream reach end of stream"); @@ -349,8 +341,7 @@ public class ClientToServerThread implements Runnable { } } - public int getClientId () { + int getClientId () { return clientId; } - } diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 82129046..8b8c7b79 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -10,6 +10,8 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXMLLoader; import javafx.scene.Node; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Pane; import seng302.gameServer.GameState; @@ -69,28 +71,31 @@ public class GameClient { */ public void runAsClient(String ipAddress, Integer portNumber) { try { - socketThread = new ClientToServerThread(ipAddress, portNumber); + startClientToServerThread(ipAddress, portNumber); + socketThread.addDisconnectionListener((cause) -> { + showConnectionError(cause); + Platform.runLater(this::loadStartScreen); + }); + socketThread.addStreamObserver(this::parsePackets); + LobbyController lobbyController = loadLobby(); + lobbyController.setPlayerListSource(clientLobbyList); + lobbyController.disableReadyButton(); + + if (regattaData != null){ + lobbyController.setTitle(regattaData.getRegattaName()); + lobbyController.setCourseName(regattaData.getCourseName()); + } + else{ + lobbyController.setTitle(ipAddress); + lobbyController.setCourseName(""); + } + + lobbyController.addCloseListener((exitCause) -> this.loadStartScreen()); + this.lobbyController = lobbyController; } catch (IOException ioe) { - ioe.printStackTrace(); - System.out.println("Unable to connect to host..."); + showConnectionError("Unable to find server"); + Platform.runLater(this::loadStartScreen); } - - socketThread.addStreamObserver(this::parsePackets); - LobbyController lobbyController = loadLobby(); - lobbyController.setPlayerListSource(clientLobbyList); - lobbyController.disableReadyButton(); - - if (regattaData != null){ - lobbyController.setTitle(regattaData.getRegattaName()); - lobbyController.setCourseName(regattaData.getCourseName()); - } - else{ - lobbyController.setTitle(ipAddress); - lobbyController.setCourseName(""); - } - - lobbyController.addCloseListener((exitCause) -> this.loadStartScreen()); - this.lobbyController = lobbyController; } /** @@ -99,38 +104,37 @@ public class GameClient { * @param portNumber Port to connect to. */ public void runAsHost(String ipAddress, Integer portNumber) { - server = new MainServerThread(); try { - socketThread = new ClientToServerThread(ipAddress, portNumber); - } catch (IOException ioe) { - ioe.printStackTrace(); - System.out.println("Unable to make local connection to host..."); - } - socketThread.addStreamObserver(this::parsePackets); - LobbyController lobbyController = loadLobby(); - lobbyController.setPlayerListSource(clientLobbyList); + startClientToServerThread(ipAddress, portNumber); + socketThread.addDisconnectionListener((cause) -> { + Platform.runLater(this::loadStartScreen); + }); + LobbyController lobbyController = loadLobby(); + lobbyController.setPlayerListSource(clientLobbyList); - if (regattaData != null){ - lobbyController.setTitle("Hosting: " + regattaData.getRegattaName()); - lobbyController.setCourseName(regattaData.getCourseName()); - } - else{ - lobbyController.setTitle("Hosting: " + ipAddress); - lobbyController.setCourseName(""); - } - - lobbyController.addCloseListener(exitCause -> { - if (exitCause == CloseStatus.READY) { - GameState.resetStartTime(); - lobbyController.disableReadyButton(); - server.startGame(); - } else if (exitCause == CloseStatus.LEAVE) { - loadStartScreen(); + if (regattaData != null) { + lobbyController.setTitle("Hosting: " + regattaData.getRegattaName()); + lobbyController.setCourseName(regattaData.getCourseName()); + } else { + lobbyController.setTitle("Hosting: " + ipAddress); + lobbyController.setCourseName(""); } - }); - this.lobbyController = lobbyController; - server.setGameClient(this); + lobbyController.addCloseListener(exitCause -> { + if (exitCause == CloseStatus.READY) { + GameState.resetStartTime(); + lobbyController.disableReadyButton(); + server.startGame(); + } else if (exitCause == CloseStatus.LEAVE) { + loadStartScreen(); + } + }); + this.lobbyController = lobbyController; + server.setGameClient(this); + } catch (IOException ioe) { + showConnectionError("Cannot connect to server as host"); + Platform.runLater(this::loadStartScreen); + } } private void loadStartScreen() { @@ -145,10 +149,24 @@ public class GameClient { holderPane.getChildren().clear(); holderPane.getChildren().add(fxmlLoader.load()); } catch (IOException e) { - e.printStackTrace(); + showConnectionError("JavaFX crashed. Please restart the app"); } } + private void showConnectionError (String message) { + Platform.runLater(() -> { + Alert alert = new Alert(AlertType.ERROR); + alert.setHeaderText("Connection Error"); + alert.setContentText(message); + alert.showAndWait(); + }); + } + + private void startClientToServerThread (String ipAddress, int portNumber) throws IOException { + socketThread = new ClientToServerThread(ipAddress, portNumber); + socketThread.addStreamObserver(this::parsePackets); + } + /** * Loads a view of the lobby into the clients pane * @@ -353,13 +371,13 @@ public class GameClient { private void keyPressed(KeyEvent e) { switch (e.getCode()) { case SPACE: // align with vmg - socketThread.sendBoatAction(BoatAction.VMG); break; + socketThread.sendBoatActionMessage(BoatAction.VMG); break; case PAGE_UP: // upwind - socketThread.sendBoatAction(BoatAction.UPWIND); break; + socketThread.sendBoatActionMessage(BoatAction.UPWIND); break; case PAGE_DOWN: // downwind - socketThread.sendBoatAction(BoatAction.DOWNWIND); break; + socketThread.sendBoatActionMessage(BoatAction.DOWNWIND); break; case ENTER: // tack/gybe - socketThread.sendBoatAction(BoatAction.TACK_GYBE); break; + socketThread.sendBoatActionMessage(BoatAction.TACK_GYBE); break; } } @@ -368,12 +386,12 @@ public class GameClient { 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 - socketThread.sendBoatAction(BoatAction.SAILS_IN); + socketThread.sendBoatActionMessage(BoatAction.SAILS_IN); allBoatsMap.get(socketThread.getClientId()).toggleSail(); break; case PAGE_UP: case PAGE_DOWN: - socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break; + socketThread.sendBoatActionMessage(BoatAction.MAINTAIN_HEADING); break; } } diff --git a/src/main/java/seng302/visualiser/GameView.java b/src/main/java/seng302/visualiser/GameView.java index 9268f3c1..3bd0ea14 100644 --- a/src/main/java/seng302/visualiser/GameView.java +++ b/src/main/java/seng302/visualiser/GameView.java @@ -277,7 +277,31 @@ public class GameView extends Pane { colour = Color.BLACK; } - //Creating mark arrows. + createMarkArrows(sequence); + + //Scale race to markers if there is no border. + if (borderPoints == null) { + rescaleRace(new ArrayList<>(markerObjects.keySet())); + } + //Move the Markers to initial position. + markerObjects.forEach(((mark, marker) -> { + Point2D p2d = findScaledXY(mark.getLat(), mark.getLng()); + marker.setLayoutX(p2d.getX()); + marker.setLayoutY(p2d.getY()); + })); + Platform.runLater(() -> { + markers.getChildren().clear(); + markers.getChildren().addAll(gates); + markers.getChildren().addAll(markerObjects.values()); + }); + } + + /** + * Calculates all the data needed for to create mark arrows. Requires that a course has been + * added to the gameview. + * @param sequence The order in which marks are traversed. + */ + private void createMarkArrows (List sequence) { for (int i=1; i < sequence.size()-1; i++) { //General case. double averageLat = 0; double averageLng = 0; @@ -297,7 +321,7 @@ public class GameView extends Pane { averageLng += mark.getLng(); } GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks); - // TODO: 16/08/17 This comparison is cancer and deserves to die. + // TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side. for (Mark mark : course.get(i).getMarks()) { markerObjects.get(mark).addArrows( mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT, @@ -306,9 +330,11 @@ public class GameView extends Pane { ); } } + createStartLineArrows(); + createFinishLineArrows(); + } - // TODO: 16/08/17 Make this cleaner - //First mark case + private void createStartLineArrows () { double averageLat = 0; double averageLng = 0; int numMarks = 0; @@ -325,10 +351,12 @@ public class GameView extends Pane { GeoUtility.getBearing(mark, firstMarkAv) ); } - //Last Mark case - numMarks = 0; - averageLat = 0; - averageLng = 0; + } + + private void createFinishLineArrows () { + double numMarks = 0; + double averageLat = 0; + double averageLng = 0; for (Mark mark : course.get(course.size()-2).getMarks()) { numMarks += 1; averageLat += mark.getLat(); @@ -342,22 +370,6 @@ public class GameView extends Pane { GeoUtility.getBearing(mark, mark) ); } - - //Scale race to markers if there is no border. - if (borderPoints == null) { - rescaleRace(new ArrayList<>(markerObjects.keySet())); - } - //Move the Markers to initial position. - markerObjects.forEach(((mark, marker) -> { - Point2D p2d = findScaledXY(mark.getLat(), mark.getLng()); - marker.setLayoutX(p2d.getX()); - marker.setLayoutY(p2d.getY()); - })); - Platform.runLater(() -> { - markers.getChildren().clear(); - markers.getChildren().addAll(gates); - markers.getChildren().addAll(markerObjects.values()); - }); } /** diff --git a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java index d7e2610e..68ff7b11 100644 --- a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java +++ b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java @@ -30,12 +30,12 @@ public class RegularPacketsTest { // ServerYacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); // double startAngle = yacht.getHeading(); // long startTime = System.currentTimeMillis(); -// clientThread.sendBoatAction(BoatAction.UPWIND); +// clientThread.sendBoatActionMessage(BoatAction.UPWIND); // Thread.sleep(200); // while (Math.abs(yacht.getHeading() - startAngle) < TEST_DISTANCE) { // Thread.sleep(1); // } -// clientThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); +// clientThread.sendBoatActionMessage(BoatAction.MAINTAIN_HEADING); // long endTime = System.currentTimeMillis(); // SleepThreadMaxDelay(); // //Allowed to be two loops of delay due to loop delay and processing delay at client + server ends. @@ -50,7 +50,7 @@ public class RegularPacketsTest { // SleepThreadMaxDelay(); // Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); // boolean startState = yacht.getSailIn(); -// clientThread.sendBoatAction(BoatAction.SAILS_IN); +// clientThread.sendBoatActionMessage(BoatAction.SAILS_IN); // SleepThreadMaxDelay(); // Assert.assertEquals(startState, !yacht.getSailIn()); // } diff --git a/src/test/java/steps/ToggleSailSteps.java b/src/test/java/steps/ToggleSailSteps.java index e4c6abed..cd4caeea 100644 --- a/src/test/java/steps/ToggleSailSteps.java +++ b/src/test/java/steps/ToggleSailSteps.java @@ -36,7 +36,7 @@ public class ToggleSailSteps { public void the_user_has_pressed(String arg1) throws Throwable { startTime = System.currentTimeMillis(); if (arg1 == "shift") { - client.sendBoatAction(BoatAction.SAILS_IN); + client.sendBoatActionMessage(BoatAction.SAILS_IN); } }