diff --git a/src/main/java/seng302/client/ClientToServerThread.java b/src/main/java/seng302/client/ClientToServerThread.java index 703df5f5..f2f73c7b 100644 --- a/src/main/java/seng302/client/ClientToServerThread.java +++ b/src/main/java/seng302/client/ClientToServerThread.java @@ -15,12 +15,17 @@ import seng302.server.messages.Message; /** * Created by kre39 on 13/07/17. */ -public class ClientToServerThread extends Thread { +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; @@ -34,11 +39,24 @@ public class ClientToServerThread extends Thread { e.printStackTrace(); } + Integer allocatedID = threeWayHandshake(); + if (allocatedID != null) { + ourID = allocatedID; + clientLog("Successful handshake. Allocated ID: " + ourID, 1); + } else { + clientLog("Unsuccessful handhsake", 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] " + message); } } @@ -91,6 +109,33 @@ public class ClientToServerThread extends Thread { } + + /** + * 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; + } + } + } + } + + + /** * Send the post-start race course information */ @@ -98,6 +143,7 @@ public class ClientToServerThread extends Thread { try { os.write(boatActionMessage.getBuffer()); } catch (IOException e) { + clientLog("COULD NOT WRITE TO SERVER", 0); e.printStackTrace(); } } @@ -139,4 +185,8 @@ public class ClientToServerThread extends Thread { readByte(); } } - } + + public Thread getThread() { + return thread; + } +} diff --git a/src/main/java/seng302/controllers/StartScreenController.java b/src/main/java/seng302/controllers/StartScreenController.java index 6f9a094c..f5c75155 100644 --- a/src/main/java/seng302/controllers/StartScreenController.java +++ b/src/main/java/seng302/controllers/StartScreenController.java @@ -40,6 +40,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 (javafx.fxml.LoadException e) { e.printStackTrace(); @@ -61,10 +62,9 @@ public class StartScreenController { try { String ipAddress = InetAddress.getLocalHost().getHostAddress(); new GameState(ipAddress); - new MainServerThread().start(); + new MainServerThread() ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950); controller.setClientToServerThread(clientToServerThread); - clientToServerThread.start(); // new GameServerThread("Fuck you"); // get the lobby controller so that we can pass the game server thread to it setContentPane("/views/LobbyView.fxml"); @@ -82,9 +82,9 @@ public class StartScreenController { // TODO: 10/07/17 wmu16 - Finish function String ipAddress = ipTextField.getText().trim().toLowerCase(); try { + // TODO: 22/07/17 wmu 16 - make this port number some static constant somewhere perhaps a config file? ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, 4950); controller.setClientToServerThread(clientToServerThread); - clientToServerThread.start(); setContentPane("/views/LobbyView.fxml"); } catch (Exception e){ e.printStackTrace(); diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 282915ec..63cb9f92 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -14,8 +14,8 @@ import seng302.server.messages.BoatActionType; public class GameState { private static Long previousUpdateTime; - private static Double windDirection = 0d; - private static Double windSpeed = 0d; + public static Double windDirection; + private static Double windSpeed; private static String hostIpAddress; private static List players; @@ -24,6 +24,12 @@ public class GameState { private static GameStages currentStage; public GameState(String hostIpAddress) { + windDirection = 170d; + windSpeed = 0d; + yachts = new HashMap<>(); + players = new ArrayList<>(); + + GameState.hostIpAddress = hostIpAddress; players = new ArrayList<>(); currentStage = GameStages.LOBBYING; @@ -49,8 +55,8 @@ public class GameState { players.remove(player); } - public static void addYacht(Integer sourceId, Yacht yatch) { - yachts.put(sourceId, yatch); + public static void addYacht(Integer sourceId, Yacht yacht) { + yachts.put(sourceId, yacht); } public static Boolean getIsRaceStarted() { @@ -78,20 +84,39 @@ public class GameState { } 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("-----------------------\n"); } public static void update() { @@ -102,4 +127,14 @@ public class GameState { 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/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index 876a1a1e..c9924a29 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -15,12 +15,14 @@ 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 implements Runnable, PacketBufferDelegate, ClientConnectionDelegate{ private static final int PORT = 4950; private static final Integer MAX_NUM_PLAYERS = 3; private static final int LOG_LEVEL = 1; + private Thread thread; + private ServerSocket serverSocket = null; private Socket socket; private ArrayList serverToClientThreads = new ArrayList<>(); @@ -36,6 +38,9 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl } packetBuffer = new PriorityBlockingQueue<>(); + + thread = new Thread(this); + thread.start(); } @@ -50,7 +55,7 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl 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 } catch (InterruptedException e) { @@ -123,7 +128,7 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl */ @Override public void clientConnected(ServerToClientThread serverToClientThread) { - serverLog("Player Connected From " + serverToClientThread.getName(), 0); + serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0); serverToClientThreads.add(serverToClientThread); } diff --git a/src/main/java/seng302/gameServer/ServerListenThread.java b/src/main/java/seng302/gameServer/ServerListenThread.java index ab4dadec..f36d08df 100644 --- a/src/main/java/seng302/gameServer/ServerListenThread.java +++ b/src/main/java/seng302/gameServer/ServerListenThread.java @@ -29,8 +29,6 @@ public class ServerListenThread extends Thread{ Socket thisClient = serverSocket.accept(); if (thisClient != null){ ServerToClientThread thisConnection = new ServerToClientThread(thisClient); - thisConnection.initialiseRace(); - thisConnection.start(); delegate.clientConnected(thisConnection); } } catch (IOException e) { diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index e22546d3..0fc2c71c 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -29,9 +29,13 @@ import seng302.utilities.GeoPoint; * 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 { + + 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; @@ -56,14 +60,31 @@ public class ServerToClientThread extends Thread { } catch (IOException e) { System.out.println("IO error in server thread upon grabbing streams"); } -// threeWayHandshake(); - Random rand = new Random(); - sourceId = rand.nextInt(1000); + //Attempt threeway handshake with connection + sourceId = GameState.getUniquePlayerID(); + if (threeWayHandshake(sourceId)) { + serverLog("Successful handshake. Client allocated id: " + sourceId, 1); + GameState.addYacht(sourceId, + new Yacht("Kappa", "Kap", new GeoPoint(0.0, 0.0), 0.0)); + GameState.addPlayer(new Player(socket)); //Is this neccesary??? + } else { + serverLog("Unsuccessful handshake. Connection rejected", 1); + closeSocket(); + return; + } Yacht yacht = new Yacht("Yacht", sourceId, sourceId.toString(), "Kap", "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)); seqNo = 0; + thread = new Thread(this); + thread.start(); + } + + static void serverLog(String message, int logLevel){ + if(logLevel <= LOG_LEVEL){ + System.out.println("[SERVER] " + message); + } } public void run() { @@ -78,7 +99,7 @@ public class ServerToClientThread extends Thread { 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 @@ -93,11 +114,9 @@ public class ServerToClientThread extends Thread { updateClient = false; } - crcBuffer = new ByteArrayOutputStream(); sync1 = readByte(); sync2 = readByte(); - //checking if it is the start of the packet if(sync1 == 0x47 && sync2 == 0x83) { int type = readByte(); @@ -125,6 +144,7 @@ public class ServerToClientThread extends Thread { } } } catch (Exception e) { + serverLog("ERROR OCCURRED, CLOSING SERVER CONNECTION: " + socket.getRemoteSocketAddress().toString(), 1); e.printStackTrace(); closeSocket(); return; @@ -167,28 +187,32 @@ 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) { @@ -200,8 +224,8 @@ public class ServerToClientThread extends Thread { public void initialiseRace(){ initialisedRace = true; } - - + + private int readByte() throws Exception { int currentByte = -1; try { @@ -252,4 +276,9 @@ public class ServerToClientThread extends Thread { sendMessage(boatLocationMessage); } } + + + public Thread getThread() { + return thread; + } } diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/models/Yacht.java index b845270b..4bcd5de6 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/models/Yacht.java @@ -7,6 +7,7 @@ import java.text.SimpleDateFormat; import java.util.Map; import javafx.scene.paint.Color; import seng302.controllers.RaceViewController; +import seng302.gameServer.GameState; import seng302.models.mark.Mark; import seng302.utilities.GeoPoint; @@ -18,6 +19,12 @@ import seng302.utilities.GeoPoint; */ public class Yacht { + private final Double TURN_STEP = 3.0; + + private Double lastHeading; + private Boolean sailIn; + + // Used in boat group private Color colour; @@ -49,6 +56,18 @@ public class Yacht { 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. * @@ -60,6 +79,7 @@ public class Yacht { this.location = location; this.heading = heading; this.velocity = 0.0; + this.sailIn = false; } /** @@ -73,12 +93,13 @@ public class Yacht { this.boatName = boatName; this.velocity = boatVelocity; this.shortName = shortName; - this.sourceId = id; - this.location = new GeoPoint(0.0, 0.0); + this.sourceID = id; + this.sailIn = false; } + public Yacht(String boatType, Integer sourceId, String hullID, String shortName, - String boatName, String country) { + String boatName, String country) { this.boatType = boatType; this.sourceId = sourceId; this.hullID = hullID; @@ -86,6 +107,7 @@ public class Yacht { this.boatName = boatName; this.country = country; this.position = "-"; + this.sailIn = false; this.location = new GeoPoint(0.0, 0.0); this.heading = 0.0; this.velocity = 0.0; @@ -95,9 +117,11 @@ public class Yacht { * @param timeInterval since last update in milliseconds */ public void update(Long timeInterval) { - Double secondsElapsed = timeInterval / 1000000.0; - Double metersCovered = velocity * secondsElapsed; - location = getGeoCoordinate(location, heading, metersCovered); + if (sailIn) { + Double secondsElapsed = timeInterval / 1000000.0; + Double metersCovered = velocity * secondsElapsed; + location = getGeoCoordinate(location, heading, metersCovered); + } } /** @@ -125,6 +149,52 @@ public class Yacht { velocity = polarsFromClosestSpd.get(closest_key); } + public Double getHeading() { + return heading; + } + + public void adjustHeading(Double amount) { + lastHeading = heading; + 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() { + if ((heading - GameState.windDirection) % 360 < 180) { + adjustHeading(TURN_STEP); + } else { + adjustHeading(-TURN_STEP); + } + } + public String getBoatType() { return boatType; @@ -249,12 +319,16 @@ public class Yacht { } public void setNextMark(Mark nextMark) { - this.nextMark = nextMark; - } + this.nextMark = nextMark; + } public Mark getNextMark(){ return nextMark; - } + } + + public Boolean getSailIn() { + return sailIn; + } @Override public String toString() {