diff --git a/.mailmap b/.mailmap index cad3a3a8..caf8a624 100644 --- a/.mailmap +++ b/.mailmap @@ -23,5 +23,5 @@ Haoming Yin Peter Galloway Peter Zhi You Tan zyt10 Zhi You Tan Ryan Tan -Alistair McIntyre alistairjmcintyre +Alistair McIntyre Calum cir27 \ No newline at end of file diff --git a/pom.xml b/pom.xml index aac3b0c7..fca06536 100644 --- a/pom.xml +++ b/pom.xml @@ -69,8 +69,8 @@ commons-cli 1.4 - + diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 3b914350..8c6f85ac 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -67,8 +67,6 @@ public class App extends Application { @Override public void start(Stage primaryStage) throws Exception { - PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); - Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml")); primaryStage.setTitle("RaceVision"); Scene scene = new Scene(root, 1530, 960); diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index ea8c1004..9a54c7d1 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -4,9 +4,12 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import seng302.gameServer.server.messages.BoatActionType; +import seng302.gameServer.server.messages.BoatAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import seng302.model.Player; import seng302.model.Yacht; +import seng302.model.mark.MarkOrder; /** * A Static class to hold information about the current state of the game (model) @@ -14,7 +17,10 @@ import seng302.model.Yacht; */ public class GameState implements Runnable { + private Logger logger = LoggerFactory.getLogger(MarkOrder.class); + private static Integer STATE_UPDATES_PER_SECOND = 60; + public static Integer MAX_PLAYERS = 8; private static Long previousUpdateTime; public static Double windDirection; @@ -25,6 +31,7 @@ public class GameState implements Runnable { private static Map yachts; private static Boolean isRaceStarted; private static GameStages currentStage; + private static MarkOrder markOrder; private static long startTime; private static Map playerStringMap = new HashMap<>(); @@ -49,12 +56,12 @@ public class GameState implements Runnable { players = new ArrayList<>(); currentStage = GameStages.LOBBYING; isRaceStarted = false; - yachts = new HashMap<>(); //set this when game stage changes to prerace previousUpdateTime = System.currentTimeMillis(); yachts = new HashMap<>(); + markOrder = new MarkOrder(); //This could be instantiated at some point with a select map? - new Thread(this).start(); + new Thread(this).start(); //Run the auto updates on the game state } public static String getHostIpAddress() { @@ -67,7 +74,8 @@ public class GameState implements Runnable { public static void addPlayer(Player player) { players.add(player); - String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName() + " " + player.getYacht().getCountry(); + String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName() + + " " + player.getYacht().getCountry(); playerStringMap.put(player, playerText); } @@ -100,6 +108,10 @@ public class GameState implements Runnable { GameState.currentStage = currentStage; } + public static MarkOrder getMarkOrder() { + return markOrder; + } + public static long getStartTime(){ return startTime; } @@ -120,7 +132,7 @@ public class GameState implements Runnable { return yachts; } - public static void updateBoat(Integer sourceId, BoatActionType actionType) { + public static void updateBoat(Integer sourceId, BoatAction actionType) { Yacht playerYacht = yachts.get(sourceId); // System.out.println("-----------------------"); switch (actionType) { diff --git a/src/main/java/seng302/gameServer/HeartbeatThread.java b/src/main/java/seng302/gameServer/HeartbeatThread.java index f15868c3..b168a197 100644 --- a/src/main/java/seng302/gameServer/HeartbeatThread.java +++ b/src/main/java/seng302/gameServer/HeartbeatThread.java @@ -42,7 +42,6 @@ public class HeartbeatThread extends Thread{ */ private void sendHeartbeatToAllPlayers(){ Message heartbeat = new Heartbeat(seqNum); - for (Player player : GameState.getPlayers()){ if (!player.getSocket().isConnected()) { playerLostConnection(player); @@ -54,7 +53,6 @@ public class HeartbeatThread extends Thread{ playerLostConnection(player); } } - updateDelegate(); seqNum++; } @@ -71,7 +69,6 @@ public class HeartbeatThread extends Thread{ public void run(){ Timer t = new Timer(); - t.schedule(new TimerTask() { @Override public void run() { diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index 838df5fb..37d693ca 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -8,6 +8,7 @@ import java.util.Observable; import java.util.Timer; import java.util.TimerTask; import seng302.model.Player; +import seng302.model.PolarTable; /** * A class describing the overall server, which creates and collects server threads for each client @@ -32,7 +33,7 @@ public class MainServerThread extends Observable implements Runnable, ClientConn } catch (IOException e) { serverLog("IO error in server thread handler upon trying to make new server socket", 0); } - + PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); terminated = false; thread = new Thread(this); thread.start(); @@ -44,6 +45,7 @@ public class MainServerThread extends Observable implements Runnable, ClientConn HeartbeatThread heartbeatThread; serverListenThread = new ServerListenThread(serverSocket, this); + heartbeatThread = new HeartbeatThread(this); heartbeatThread.start(); @@ -102,9 +104,11 @@ public class MainServerThread extends Observable implements Runnable, ClientConn public void clientConnected(ServerToClientThread serverToClientThread) { serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0); serverToClientThreads.add(serverToClientThread); - this.addObserver(serverToClientThread); - setChanged(); - notifyObservers(); + serverToClientThread.addConnectionListener(() -> { + for (ServerToClientThread thread : serverToClientThreads) { + thread.sendSetupMessages(); + } + }); } /** @@ -121,11 +125,15 @@ public class MainServerThread extends Observable implements Runnable, ClientConn serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0); GameState.removeYacht(player.getYacht().getSourceId()); GameState.removePlayer(player); + ServerToClientThread closedConnection = null; for (ServerToClientThread serverToClientThread : serverToClientThreads) { if (serverToClientThread.getSocket() == player.getSocket()) { - this.deleteObserver(serverToClientThread); + closedConnection = serverToClientThread; + } else { + serverToClientThread.sendSetupMessages(); } } + serverToClientThreads.remove(closedConnection); setChanged(); notifyObservers(); } diff --git a/src/main/java/seng302/gameServer/ServerPacketParser.java b/src/main/java/seng302/gameServer/ServerPacketParser.java index 0d631eb1..84f8fd17 100644 --- a/src/main/java/seng302/gameServer/ServerPacketParser.java +++ b/src/main/java/seng302/gameServer/ServerPacketParser.java @@ -1,37 +1,26 @@ package seng302.gameServer; import java.util.Arrays; + +import seng302.gameServer.server.messages.ClientType; +import seng302.gameServer.server.messages.Message; import seng302.model.stream.packets.StreamPacket; -import seng302.gameServer.server.messages.BoatActionType; +import seng302.gameServer.server.messages.BoatAction; public class ServerPacketParser { - - public static BoatActionType extractBoatAction(StreamPacket packet) { + public static BoatAction extractBoatAction(StreamPacket packet) { byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; - long actionTypeValue = bytesToLong(Arrays.copyOfRange(payload, 0, 1)); - return BoatActionType.getType((int) actionTypeValue); + long actionTypeValue = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1)); + return BoatAction.getType((int) actionTypeValue); } - /** - * takes an array of up to 7 bytes and returns a positive - * long constructed from the input bytes - * - * @return a positive long if there is less than 7 bytes -1 otherwise - */ - private static long bytesToLong(byte[] bytes) { - long partialLong = 0; - int index = 0; - for (byte b : bytes) { - if (index > 6) { - return -1; - } - partialLong = partialLong | (b & 0xFFL) << (index * 8); - index++; - } - return partialLong; + public static ClientType extractClientType(StreamPacket packet){ + byte[] payload = packet.getPayload(); + long value = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1)); + return ClientType.getClientType((int) value); } } diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index ff2c4f23..a3f87e2b 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -12,12 +12,23 @@ import java.net.SocketException; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import java.util.Observable; -import java.util.Observer; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.zip.CRC32; import java.util.zip.Checksum; +import seng302.gameServer.server.messages.BoatAction; +import seng302.gameServer.server.messages.BoatLocationMessage; +import seng302.gameServer.server.messages.BoatStatus; +import seng302.gameServer.server.messages.BoatSubMessage; +import seng302.gameServer.server.messages.ClientType; +import seng302.gameServer.server.messages.Message; +import seng302.gameServer.server.messages.RaceStatus; +import seng302.gameServer.server.messages.RaceStatusMessage; +import seng302.gameServer.server.messages.RaceType; +import seng302.gameServer.server.messages.RegistrationResponseMessage; +import seng302.gameServer.server.messages.RegistrationResponseStatus; +import seng302.gameServer.server.messages.XMLMessage; +import seng302.gameServer.server.messages.XMLMessageSubType; import seng302.model.Player; import seng302.model.Yacht; import seng302.model.stream.packets.PacketType; @@ -25,23 +36,21 @@ import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.xml.generator.Race; import seng302.model.stream.xml.generator.Regatta; import seng302.utilities.XMLGenerator; -import seng302.gameServer.server.messages.BoatActionType; -import seng302.gameServer.server.messages.BoatLocationMessage; -import seng302.gameServer.server.messages.BoatStatus; -import seng302.gameServer.server.messages.BoatSubMessage; -import seng302.gameServer.server.messages.Message; -import seng302.gameServer.server.messages.RaceStatus; -import seng302.gameServer.server.messages.RaceStatusMessage; -import seng302.gameServer.server.messages.RaceType; -import seng302.gameServer.server.messages.XMLMessage; -import seng302.gameServer.server.messages.XMLMessageSubType; /** * A class describing a single connection to a Client for the purposes of sending and receiving on * its own thread. All server threads created and owned by the server thread handler which can * trigger client updates on its threads Created by wmu16 on 13/07/17. */ -public class ServerToClientThread implements Runnable, Observer { +public class ServerToClientThread implements Runnable { + + /** + * Called to notify listeners when this thread receives a connection correctly. + */ + @FunctionalInterface + interface ConnectionListener { + void notifyConnection (); + } private static final Integer LOG_LEVEL = 1; private static final Integer MAX_ID_ATTEMPTS = 10; @@ -54,65 +63,64 @@ public class ServerToClientThread implements Runnable, Observer { private ByteArrayOutputStream crcBuffer; - private Boolean userIdentified = false; - private Boolean connected = true; - private Boolean updateClient = true; // private Boolean initialisedRace = true; private Integer seqNo; private Integer sourceId; + private ClientType clientType; + private Boolean isRegistered = false; + private XMLGenerator xml; + private List connectionListeners = new ArrayList<>(); + public ServerToClientThread(Socket socket) { this.socket = socket; + seqNo = 0; + + try{ + is = socket.getInputStream(); + os = socket.getOutputStream(); + } catch (IOException e) { + return; + } + + thread = new Thread(this); + thread.start(); + } + + private void setUpPlayer(){ BufferedReader fn; String fName = ""; BufferedReader ln; String lName = ""; - try { - is = socket.getInputStream(); - os = socket.getOutputStream(); - fn = new BufferedReader( - new InputStreamReader( - ServerToClientThread.class.getResourceAsStream( - "/server_config/CSV_Database_of_First_Names.csv" - ) - ) - ); - List all = fn.lines().collect(Collectors.toList()); - fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); - ln = new BufferedReader( - new InputStreamReader( - ServerToClientThread.class.getResourceAsStream( - "/server_config/CSV_Database_of_Last_Names.csv" - ) - ) - ); - all = ln.lines().collect(Collectors.toList()); - lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); - } catch (IOException e) { - serverLog("IO error in server thread upon grabbing streams", 1); - } - //Attempt threeway handshake with connection - sourceId = GameState.getUniquePlayerID(); - if (threeWayHandshake(sourceId)) { - serverLog("Successful handshake. Client allocated id: " + sourceId, 0); - Yacht yacht = new Yacht( - "Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" - ); -// Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0); - GameState.addYacht(sourceId, yacht); - GameState.addPlayer(new Player(socket, yacht)); - } else { - serverLog("Unsuccessful handshake. Connection rejected", 1); - closeSocket(); - return; - } - seqNo = 0; - thread = new Thread(this); - thread.start(); + fn = new BufferedReader( + new InputStreamReader( + ServerToClientThread.class.getResourceAsStream( + "/server_config/CSV_Database_of_First_Names.csv" + ) + ) + ); + List all = fn.lines().collect(Collectors.toList()); + fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); + ln = new BufferedReader( + new InputStreamReader( + ServerToClientThread.class.getResourceAsStream( + "/server_config/CSV_Database_of_Last_Names.csv" + ) + ) + ); + all = ln.lines().collect(Collectors.toList()); + lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); + + + Yacht yacht = new Yacht( + "Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" + ); + GameState.addYacht(sourceId, yacht); + GameState.addPlayer(new Player(socket, yacht)); } static void serverLog(String message, int logLevel) { @@ -122,9 +130,33 @@ public class ServerToClientThread implements Runnable, Observer { } } - @Override - public void update(Observable o, Object arg) { - sendSetupMessages(); + private void completeRegistration(ClientType clientType) throws IOException { + // Fail if not a player + if (!clientType.equals(ClientType.PLAYER)){ + RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_GENERAL); + os.write(responseMessage.getBuffer()); + return; + } + + if (GameState.getPlayers().size() >= GameState.MAX_PLAYERS){ + RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL); + os.write(responseMessage.getBuffer()); + return; + } + + Integer sourceId = GameState.getUniquePlayerID(); + RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(sourceId, RegistrationResponseStatus.SUCCESS_PLAYING); + + this.clientType = clientType; + this.sourceId = sourceId; + isRegistered = true; + os.write(responseMessage.getBuffer()); + + setUpPlayer(); + + for (ConnectionListener listener : connectionListeners) { + listener.notifyConnection(); + } } public void run() { @@ -132,23 +164,10 @@ public class ServerToClientThread implements Runnable, Observer { int sync2; // TODO: 14/07/17 wmu16 - Work out how to fix this while loop + while (socket.isConnected()) { try { - //Perform a write if it is time to as delegated by the MainServerThread - if (updateClient) { - // TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream -// ChatterMessage chatterMessage = new ChatterMessage(4, 14, "Hello, it's me"); -// sendMessage(chatterMessage); -// try { -// GameState.outputState(os); -// } catch (IOException e) { -// System.out.println("IO error in server thread upon writing to output stream"); -// } -// sendBoatLocationPackets(); - updateClient = false; - } - crcBuffer = new ByteArrayOutputStream(); sync1 = readByte(); sync2 = readByte(); @@ -168,11 +187,18 @@ public class ServerToClientThread implements Runnable, Observer { //System.out.println("RECEIVED A PACKET"); switch (PacketType.assignPacketType(type, payload)) { case BOAT_ACTION: - BoatActionType actionType = ServerPacketParser - .extractBoatAction( - new StreamPacket(type, payloadLength, timeStamp, payload)); + BoatAction actionType = ServerPacketParser + .extractBoatAction( + new StreamPacket(type, payloadLength, timeStamp, payload)); GameState.updateBoat(sourceId, actionType); break; + + case RACE_REGISTRATION_REQUEST: + ClientType requestedType = ServerPacketParser.extractClientType( + new StreamPacket(type, payloadLength, timeStamp, payload)); + + completeRegistration(requestedType); + break; } } else { serverLog("Packet has been dropped", 1); @@ -187,7 +213,7 @@ public class ServerToClientThread implements Runnable, Observer { } } - private void sendSetupMessages() { + public void sendSetupMessages() { xml = new XMLGenerator(); Race race = new Race(); @@ -215,40 +241,6 @@ public class ServerToClientThread implements Runnable, Observer { public void updateClient() { sendBoatLocationPackets(); - 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 - * - * @param id the id to try and assign to the connection - * @return A boolean indicating if it was a successful handshake - */ - 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) { - serverLog("Three way handshake failed", 1); - } - - 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; } private void closeSocket() { @@ -259,7 +251,6 @@ public class ServerToClientThread implements Runnable, Observer { } } - private int readByte() throws Exception { int currentByte = -1; try { @@ -310,7 +301,6 @@ public class ServerToClientThread implements Runnable, Observer { private void sendBoatLocationPackets() { ArrayList yachts = new ArrayList<>(GameState.getYachts().values()); for (Yacht yacht : yachts) { -// System.out.println("[SERVER] Lat: " + yacht.getLocation().getLat() + " Lon: " + yacht.getLocation().getLng()); BoatLocationMessage boatLocationMessage = new BoatLocationMessage( yacht.getSourceId(), @@ -331,7 +321,6 @@ public class ServerToClientThread implements Runnable, Observer { public void sendRaceStatusMessage() { // variables taken from GameServerThread - List boatSubMessages = new ArrayList<>(); BoatStatus boatStatus; RaceStatus raceStatus; @@ -366,4 +355,12 @@ public class ServerToClientThread implements Runnable, Observer { public Socket getSocket() { return socket; } + + public void addConnectionListener(ConnectionListener listener) { + connectionListeners.add(listener); + } + + public void removeConnectionListener(ConnectionListener listener) { + connectionListeners.remove(listener); + } } diff --git a/src/main/java/seng302/gameServer/server/messages/BoatActionType.java b/src/main/java/seng302/gameServer/server/messages/BoatAction.java similarity index 62% rename from src/main/java/seng302/gameServer/server/messages/BoatActionType.java rename to src/main/java/seng302/gameServer/server/messages/BoatAction.java index 53fc6018..cc53668c 100644 --- a/src/main/java/seng302/gameServer/server/messages/BoatActionType.java +++ b/src/main/java/seng302/gameServer/server/messages/BoatAction.java @@ -6,29 +6,30 @@ import java.util.Map; /** * Created by kre39 on 12/07/17. */ -public enum BoatActionType { +public enum BoatAction { VMG(1), SAILS_IN(2), SAILS_OUT(3), TACK_GYBE(4), UPWIND(5), - DOWNWIND(6); + DOWNWIND(6), + MAINTAIN_HEADING(7); private final int type; - private static final Map intToTypeMap = new HashMap<>(); + private static final Map intToTypeMap = new HashMap<>(); static { - for (BoatActionType type : BoatActionType.values()) { + for (BoatAction type : BoatAction.values()) { intToTypeMap.put(type.getValue(), type); } } - BoatActionType(int type){ + BoatAction(int type){ this.type = type; } - public static BoatActionType getType(int value) { + public static BoatAction getType(int value) { return intToTypeMap.get(value); } diff --git a/src/main/java/seng302/gameServer/server/messages/BoatActionMessage.java b/src/main/java/seng302/gameServer/server/messages/BoatActionMessage.java index cfa596f7..2fc084e5 100644 --- a/src/main/java/seng302/gameServer/server/messages/BoatActionMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/BoatActionMessage.java @@ -6,9 +6,9 @@ package seng302.gameServer.server.messages; public class BoatActionMessage extends Message{ private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION; private final int MESSAGE_SIZE = 1; - private BoatActionType actionType; + private BoatAction actionType; - public BoatActionMessage(BoatActionType actionType) { + public BoatActionMessage(BoatAction actionType) { this.actionType = actionType; setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id allocateBuffer(); diff --git a/src/main/java/seng302/gameServer/server/messages/ClientType.java b/src/main/java/seng302/gameServer/server/messages/ClientType.java new file mode 100644 index 00000000..b96ca5c7 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/ClientType.java @@ -0,0 +1,33 @@ +package seng302.gameServer.server.messages; + +public enum ClientType { + SPECTATOR(0x00), + PLAYER(0x01), + CONTROL_TUTORIAL(0x02), + GHOST_MODE(0x03); + + private int type; + + ClientType(int type){ + this.type = type; + } + + public int getCode(){ + return type; + } + + public static ClientType getClientType(int typeCode){ + switch (typeCode){ + case 0x00: + return SPECTATOR; + case 0x01: + return PLAYER; + case 0x02: + return CONTROL_TUTORIAL; + case 0x03: + return GHOST_MODE; + default: + return PLAYER; + } + } +} diff --git a/src/main/java/seng302/gameServer/server/messages/MessageType.java b/src/main/java/seng302/gameServer/server/messages/MessageType.java index 1d25e61d..4552d781 100644 --- a/src/main/java/seng302/gameServer/server/messages/MessageType.java +++ b/src/main/java/seng302/gameServer/server/messages/MessageType.java @@ -17,7 +17,9 @@ public enum MessageType { MARK_ROUNDING(38), COURSE_WIND(44), AVERAGE_WIND(47), - BOAT_ACTION(100); + BOAT_ACTION(100), + REGISTRATION_REQUEST(101), + REGISTRATION_RESPONSE(102); private int code; @@ -32,4 +34,6 @@ public enum MessageType { int getCode(){ return this.code; } + + } diff --git a/src/main/java/seng302/gameServer/server/messages/RegistrationRequestMessage.java b/src/main/java/seng302/gameServer/server/messages/RegistrationRequestMessage.java new file mode 100644 index 00000000..59757a18 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/RegistrationRequestMessage.java @@ -0,0 +1,22 @@ +package seng302.gameServer.server.messages; + + +public class RegistrationRequestMessage extends Message { + private static int MESSAGE_LENGTH = 2; + + public RegistrationRequestMessage(ClientType type){ + setHeader(new Header(MessageType.REGISTRATION_REQUEST, 1, (short) getSize())); + + allocateBuffer(); + writeHeaderToBuffer(); + + putInt(type.getCode(), 2); + + writeCRC(); + } + + @Override + public int getSize() { + return MESSAGE_LENGTH; + } +} diff --git a/src/main/java/seng302/gameServer/server/messages/RegistrationResponseMessage.java b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseMessage.java new file mode 100644 index 00000000..e2174da4 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseMessage.java @@ -0,0 +1,20 @@ +package seng302.gameServer.server.messages; + +public class RegistrationResponseMessage extends Message{ + + public RegistrationResponseMessage(int clientSourceID, RegistrationResponseStatus status){ + setHeader(new Header(MessageType.REGISTRATION_RESPONSE, 1, (short) getSize())); + allocateBuffer(); + writeHeaderToBuffer(); + + putInt(clientSourceID, 4); + putInt(status.getCode(), 1); + + writeCRC(); + } + + @Override + public int getSize() { + return 5; + } +} diff --git a/src/main/java/seng302/gameServer/server/messages/RegistrationResponseStatus.java b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseStatus.java new file mode 100644 index 00000000..2ae47a92 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseStatus.java @@ -0,0 +1,44 @@ +package seng302.gameServer.server.messages; + +public enum RegistrationResponseStatus { + SUCCESS_SPECTATING(0x00), + SUCCESS_PLAYING(0x01), + SUCCESS_TUTORIAL(0x02), + SUCCESS_GHOSTING(0x03), + + FAILURE_GENERAL(0x10), + FAILURE_FULL(0x11); + + private int code; + + RegistrationResponseStatus(int code){ + this.code = code; + } + + /** + * Get the message code (From the API Spec) + * @return the message code + */ + int getCode(){ + return this.code; + } + + public static RegistrationResponseStatus getResponseStatus(int typeCode){ + switch (typeCode){ + case 0x00: + return SUCCESS_SPECTATING; + case 0x01: + return SUCCESS_PLAYING; + case 0x02: + return SUCCESS_TUTORIAL; + case 0x03: + return SUCCESS_GHOSTING; + case 0x10: + return FAILURE_GENERAL; + case 0x11: + return FAILURE_FULL; + default: + return FAILURE_GENERAL; + } + } +} diff --git a/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java b/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java index 66def8a1..36164af2 100644 --- a/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java +++ b/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java @@ -8,10 +8,10 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import seng302.model.mark.CompoundMark; import seng302.gameServer.server.simulator.Corner; -import seng302.model.mark.Mark; import seng302.gameServer.server.simulator.RoundingType; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.Mark; /** * Parses the race xml file to get course details @@ -79,21 +79,22 @@ public class CourseParser extends FileParser { if (node.getNodeType() == Node.ELEMENT_NODE) { Element e = (Element) node; Integer markID = Integer.valueOf(e.getAttribute("CompoundMarkID")); - String name = e.getAttribute("Name"); - CompoundMark cMark = new CompoundMark(markID, name); NodeList marks = e.getElementsByTagName("Mark"); - for (int i = 0; i < marks.getLength(); i++) { - Mark mark = getMark(marks.item(i)); - if (mark != null) - cMark.addSubMarks(mark); - } - return cMark; - } - System.out.println("Failed to create compound mark."); - return null; - } + List subMarks = new ArrayList<>(); + for (int i = 0; i < marks.getLength(); i++) { + Mark mark = getMark(marks.item(i)); + if (mark != null) { + subMarks.add(mark); + } + } + + return new CompoundMark(markID, name, subMarks); + } + System.out.println("Failed to create compound mark."); + return null; + } private Mark getMark(Node node) { diff --git a/src/main/java/seng302/model/PolarTable.java b/src/main/java/seng302/model/PolarTable.java index dee22278..9334cc54 100644 --- a/src/main/java/seng302/model/PolarTable.java +++ b/src/main/java/seng302/model/PolarTable.java @@ -71,8 +71,6 @@ public final class PolarTable { } catch (IOException e) { System.out.println("[PolarTable] IO exception"); } - - } @@ -155,7 +153,6 @@ public final class PolarTable { public static Double getClosestWindSpeedInPolar(Double thisWindSpeed) { Double smallestDif = Double.POSITIVE_INFINITY; Double closestWind = 0d; - for (Double polarWindSpeed : polarTable.keySet()) { Double difference = Math.abs(polarWindSpeed - thisWindSpeed); if (difference < smallestDif) { diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 519df57c..ed058773 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -10,6 +10,8 @@ import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyLongProperty; import javafx.beans.property.ReadOnlyLongWrapper; import javafx.scene.paint.Color; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import seng302.gameServer.GameState; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; @@ -29,7 +31,10 @@ public class Yacht { void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity, boolean sailIn); } - private static final Double ROUNDING_DISTANCE = 15d; // TODO: 3/08/17 wmu16 - Look into this value further + private Logger logger = LoggerFactory.getLogger(Yacht.class); + + private static final Double ROUNDING_DISTANCE = 50d; // TODO: 3/08/17 wmu16 - Look into this value further + //BOTH AFAIK private String boatType; @@ -40,26 +45,29 @@ public class Yacht { private String country; private Long estimateTimeAtFinish; - private Long lastMark; + private Integer currentMarkSeqID = 0; private Long markRoundTime; - private Double distanceToNextMark; + private Double distanceToCurrentMark; private Long timeTillNext; - private CompoundMark nextMark; private Double heading; private Integer legNumber = 0; //SERVER SIDE - private final Double TURN_STEP = 5.0; + public static final Double TURN_STEP = 5.0; //This should be in some utils class somewhere 2bh. Public for tests sake. private Double lastHeading; private Boolean sailIn = false; private GeoPoint location; private Integer boatStatus; private Double velocity; + private Boolean isAuto; + private Double autoHeading; + //MARK ROUNDING INFO private GeoPoint lastLocation; //For purposes of mark rounding calculations private Boolean hasEnteredRoundingZone; //The distance that the boat must be from the mark to round - private Boolean hasPassedFirstLine; //The line extrapolated from the next mark to the current mark - private Boolean hasPassedSecondLine; //The line extrapolated from the last mark to the current mark + private Boolean hasPassedLine; + private Boolean hasPassedThroughGate; + private Boolean finishedRace; //CLIENT SIDE private List locationListeners = new ArrayList<>(); @@ -79,14 +87,17 @@ public class Yacht { this.shortName = shortName; this.boatName = boatName; this.country = country; + this.sailIn = false; + this.isAuto = false; this.location = new GeoPoint(57.670341, 11.826856); this.lastLocation = location; this.heading = 120.0; //In degrees this.velocity = 0d; //in mms-1 this.hasEnteredRoundingZone = false; - this.hasPassedFirstLine = false; - this.hasPassedSecondLine = false; + this.hasPassedLine = false; + this.hasPassedThroughGate = false; + this.finishedRace = false; } /** @@ -122,13 +133,15 @@ public class Yacht { } } + runAutoPilot(); + //UPDATE BOAT LOCATION + lastLocation = location; location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); //CHECK FOR MARK ROUNDING - distanceToNextMark = calcDistanceToNextMark(); - if (distanceToNextMark < ROUNDING_DISTANCE) { - hasEnteredRoundingZone = true; + if (!finishedRace) { + checkForLegProgression(); } // TODO: 3/08/17 wmu16 - Implement line cross check here @@ -136,14 +149,16 @@ public class Yacht { /** - * Calculates the distance to the next mark (closest of the two if a gate mark). - * + * Calculates the distance to the next mark (closest of the two if a gate mark). For purposes + * of mark rounding * @return A distance in metres. Returns -1 if there is no next mark + * @throws IndexOutOfBoundsException If the next mark is null (ie the last mark in the race) + * Check first using {@link seng302.model.mark.MarkOrder#isLastMark(Integer)} */ - public Double calcDistanceToNextMark() { - if (nextMark == null) { - return -1d; - } else if (nextMark.isGate()) { + public Double calcDistanceToCurrentMark() throws IndexOutOfBoundsException { + CompoundMark nextMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID); + + if (nextMark.isGate()) { Mark sub1 = nextMark.getSubMark(1); Mark sub2 = nextMark.getSubMark(2); Double distance1 = GeoUtility.getDistance(location, sub1); @@ -154,15 +169,198 @@ public class Yacht { } } + + /** + * 4 Different cases of progression in the race + * 1 - Passing the start line + * 2 - Passing any in-race Gate + * 3 - Passing any in-race Mark + * 4 - Passing the finish line + */ + private void checkForLegProgression() { + CompoundMark currentMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID); + if (currentMarkSeqID == 0) { + checkStartLineCrossing(currentMark); + } else if (GameState.getMarkOrder().isLastMark(currentMarkSeqID)) { + checkFinishLineCrossing(currentMark); + } else if (currentMark.isGate()) { + checkGateRounding(currentMark); + } else { + checkMarkRounding(currentMark); + } + } + + /** + * If we pass the start line gate in the correct direction, progress + * + * @param currentMark The current gate + */ + private void checkStartLineCrossing(CompoundMark currentMark) { + Mark mark1 = currentMark.getSubMark(1); + Mark mark2 = currentMark.getSubMark(2); + CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); + + Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location); + if (crossedLine > 0) { + Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint()); + if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) { + currentMarkSeqID++; + logMarkRounding(currentMark); + } + } + } + + + /** + * This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute + * of the yacht if so. + * A visual representation of this algorithm can be seen on the Wiki under + * 'mark passing algorithm' + */ + private void checkMarkRounding(CompoundMark currentMark) { + distanceToCurrentMark = calcDistanceToCurrentMark(); + GeoPoint nextPoint = GameState.getMarkOrder().getNextMark(currentMarkSeqID).getMidPoint(); + GeoPoint prevPoint = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID) + .getMidPoint(); + GeoPoint midPoint = GeoUtility.getDirtyMidPoint(nextPoint, prevPoint); + + //1 TEST FOR ENTERING THE ROUNDING DISTANCE + if (distanceToCurrentMark < ROUNDING_DISTANCE) { + hasEnteredRoundingZone = true; + } + + //In case current mark is a gate, loop through all marks just in case + for (Mark thisCurrentMark : currentMark.getMarks()) { + if (GeoUtility.isPointInTriangle(lastLocation, location, midPoint, thisCurrentMark)) { + hasPassedLine = true; + } + } + + if (hasPassedLine && hasEnteredRoundingZone) { + currentMarkSeqID++; + hasPassedLine = false; + hasEnteredRoundingZone = false; + hasPassedThroughGate = false; + logMarkRounding(currentMark); + } + } + + + /** + * Checks if a gate line has been crossed and in the correct direction + * + * @param currentMark The current gate + */ + private void checkGateRounding(CompoundMark currentMark) { + Mark mark1 = currentMark.getSubMark(1); + Mark mark2 = currentMark.getSubMark(2); + CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); + CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); + + Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location); + + //We have crossed the line + if (crossedLine > 0) { + Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint()); + + //Check we cross the line in the correct direction + if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) { + hasPassedThroughGate = true; + } + } + + Boolean prevMarkSide = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint()); + Boolean nextMarkSide = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint()); + + if (hasPassedThroughGate) { + //Check if we need to round this gate after passing through + if (prevMarkSide == nextMarkSide) { + checkMarkRounding(currentMark); + } else { + currentMarkSeqID++; + logMarkRounding(currentMark); + } + } + } + + /** + * If we pass the finish gate in the correct direction + * + * @param currentMark The current gate + */ + private void checkFinishLineCrossing(CompoundMark currentMark) { + Mark mark1 = currentMark.getSubMark(1); + Mark mark2 = currentMark.getSubMark(2); + CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); + + Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location); + if (crossedLine > 0) { + Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint()); + if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) { + currentMarkSeqID++; + finishedRace = true; + logMarkRounding(currentMark); + logger.debug(sourceId + " finished"); + // TODO: 8/08/17 wmu16 - Do something! + } + } + } + + + /** + * Adjusts the heading of the boat by a given amount, while recording the boats + * last heading. + * + * @param amount the amount by which to adjust the boat heading. + */ public void adjustHeading(Double amount) { Double newVal = heading + amount; lastHeading = heading; heading = (double) Math.floorMod(newVal.longValue(), 360L); } + /** + * Swaps the boats direction from one side of the wind to the other. + */ public void tackGybe(Double windDirection) { - Double normalizedHeading = normalizeHeading(); - adjustHeading(-2 * normalizedHeading); + if (isAuto) { + disableAutoPilot(); + } else { + Double normalizedHeading = normalizeHeading(); + Double newVal = (-2 * normalizedHeading) + heading; + Double newHeading = (double) Math.floorMod(newVal.longValue(), 360L); + setAutoPilot(newHeading); + } + } + + /** + * Enables the boats auto pilot feature, which will move the boat towards a given heading. + * @param thisHeading The heading to move the boat towards. + */ + private void setAutoPilot(Double thisHeading) { + isAuto = true; + autoHeading = thisHeading; + } + + /** + * Disables the auto pilot function. + */ + public void disableAutoPilot() { + isAuto = false; + } + + /** + * Moves the boat towards the given heading when the auto pilot was set. Disables the auto pilot + * in the event that the boat is within the range of 1 turn step of its goal. + */ + public void runAutoPilot() { + if (isAuto) { + turnTowardsHeading(autoHeading); + if (Math.abs(heading - autoHeading) + <= TURN_STEP) { //Cancel when within 1 turn step of target. + isAuto = false; + } + } } public void toggleSailIn() { @@ -170,6 +368,7 @@ public class Yacht { } public void turnUpwind() { + disableAutoPilot(); Double normalizedHeading = normalizeHeading(); if (normalizedHeading == 0) { if (lastHeading < 180) { @@ -191,6 +390,7 @@ public class Yacht { } public void turnDownwind() { + disableAutoPilot(); Double normalizedHeading = normalizeHeading(); if (normalizedHeading == 0) { if (lastHeading < 180) { @@ -211,39 +411,59 @@ public class Yacht { } } + /** + * Takes the VMG from the polartable for upwind or downwind depending on the boats direction, + * and uses this to calculate a heading to move the yacht towards. + */ public void turnToVMG() { - Double normalizedHeading = normalizeHeading(); - Double optimalHeading; - HashMap optimalPolarMap; - - if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind - optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots()); - optimalHeading = optimalPolarMap.keySet().iterator().next(); + if (isAuto) { + disableAutoPilot(); } else { - optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots()); - optimalHeading = optimalPolarMap.keySet().iterator().next(); - } - // Take optimal heading and turn into correct - optimalHeading = - optimalHeading + (double) Math.floorMod(GameState.getWindDirection().longValue(), 360L); + Double normalizedHeading = normalizeHeading(); + Double optimalHeading; + HashMap optimalPolarMap; - turnTowardsHeading(optimalHeading); - - } - - private void turnTowardsHeading(Double newHeading) { - System.out.println(newHeading); - if (heading < 90 && newHeading > 270) { - adjustHeading(-TURN_STEP); - } else { - if (heading < newHeading) { - adjustHeading(TURN_STEP); + if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind + optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots()); } else { - adjustHeading(-TURN_STEP); + optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots()); } + optimalHeading = optimalPolarMap.keySet().iterator().next(); + + if (normalizedHeading > 180) { + optimalHeading = 360 - optimalHeading; + } + + // Take optimal heading and turn into a boat heading rather than a wind heading. + optimalHeading = + optimalHeading + GameState.getWindDirection(); + + setAutoPilot(optimalHeading); } } + /** + * Takes a given heading and rotates the boat towards that heading. + * This does not care about being upwind or downwind, just which direction will reach a given + * heading faster. + * + * @param newHeading The heading to turn the yacht towards. + */ + private void turnTowardsHeading(Double newHeading) { + Double newVal = heading - newHeading; + if (Math.floorMod(newVal.longValue(), 360L) > 180) { + adjustHeading(TURN_STEP / 5); + } else { + adjustHeading(-TURN_STEP / 5); + } + } + + /** + * Returns a heading normalized for the wind direction. Heading direction into the wind is 0, + * directly away is 180. + * + * @return The normalized heading accounting for wind direction. + */ private Double normalizeHeading() { Double normalizedHeading = heading - GameState.windDirection; normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L); @@ -359,14 +579,6 @@ public class Yacht { this.lastMarkRounded = lastMarkRounded; } - public void setNextMark(CompoundMark nextMark) { - this.nextMark = nextMark; - } - - public CompoundMark getNextMark(){ - return nextMark; - } - public GeoPoint getLocation() { return location; } @@ -434,9 +646,8 @@ public class Yacht { this.velocity = velocity; } - - public Double getDistanceToNextMark() { - return distanceToNextMark; + public Double getDistanceToCurrentMark() { + return distanceToCurrentMark; } public void updateLocation(double lat, double lng, double heading, double velocity) { @@ -449,6 +660,20 @@ public class Yacht { } } + private void logMarkRounding(CompoundMark currentMark) { + String typeString = "mark"; + if (currentMark.isGate()) { + typeString = "gate"; + } + logger.debug( + String.format("BoatID %d passed %s %s with id %d. Now on leg %d", + sourceId, + typeString, + currentMark.getMarks().get(0).getName(), + currentMark.getId(), + currentMarkSeqID)); + } + public void addLocationListener (YachtLocationListener listener) { locationListeners.add(listener); } diff --git a/src/main/java/seng302/model/mark/CompoundMark.java b/src/main/java/seng302/model/mark/CompoundMark.java index af4cd8a8..fe5147de 100644 --- a/src/main/java/seng302/model/mark/CompoundMark.java +++ b/src/main/java/seng302/model/mark/CompoundMark.java @@ -1,8 +1,9 @@ package seng302.model.mark; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import seng302.model.GeoPoint; +import seng302.utilities.GeoUtility; public class CompoundMark { @@ -10,18 +11,17 @@ public class CompoundMark { private String name; private List marks = new ArrayList<>(); + private GeoPoint midPoint; - public CompoundMark(int markID, String name) { - this.compoundMarkId = markID; - this.name = name; - } - - public void addSubMarks(Mark... marks) { - this.marks.addAll(Arrays.asList(marks)); - } - - public void addSubMarks(List marks) { - this.marks.addAll(marks); + public CompoundMark(int markID, String name, List marks) { + this.compoundMarkId = markID; + this.name = name; + this.marks.addAll(marks); + if (marks.size() > 1) { + this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1)); + } else { + this.midPoint = marks.get(0); + } } /** @@ -68,6 +68,16 @@ public class CompoundMark { } } + /** + * NOTE: This is a 'dirty' mid point as it is simply calculated as an xy point would be. + * NO CHECKING FOR LAT / LNG WRAPPING IS DONE IN CREATION OF THIS MIDPOINT + * + * @return GeoPoint of the midpoint of the two marks, or the one mark if there is only one + */ + public GeoPoint getMidPoint() { + return midPoint; + } + /** * Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a * specific singleMark or the list of marks. @@ -87,38 +97,6 @@ public class CompoundMark { return marks; } - -// @Override -// public boolean equals(Object other) { -// if (other == null) { -// return false; -// } -// -// if (!(other instanceof Mark)){ -// return false; -// } -// -// Mark otherMark = (Mark) other; -// -// if (otherMark.getLat() != getLat() || otherMark.getLongitude() != getLongitude()) { -// return false; -// } -// -// if (otherMark.getCompoundMarkID() != getCompoundMarkID()){ -// return false; -// } -// -// if (otherMark.getId() != getId()){ -// return false; -// } -// -// if (!otherMark.getName().equals(name)){ -// return false; -// } -// -// return true; -// } - @Override public int hashCode() { int hash = 0; diff --git a/src/main/java/seng302/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java index ca2b4356..1b744fc2 100644 --- a/src/main/java/seng302/model/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -1,5 +1,14 @@ package seng302.model.mark; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -10,21 +19,12 @@ import seng302.model.stream.xml.parser.RaceXMLData; import seng302.utilities.XMLGenerator; import seng302.utilities.XMLParser; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - /** * Class to hold the order of the marks in the race. */ public class MarkOrder { - private List raceMarkOrder; + + private List raceMarkOrder; private Logger logger = LoggerFactory.getLogger(MarkOrder.class); public MarkOrder(){ @@ -35,7 +35,7 @@ public class MarkOrder { * @return An ordered list of marks in the race * OR null if the mark order could not be loaded */ - public List getMarkOrder(){ + public List getMarkOrder() { if (raceMarkOrder == null){ logger.warn("Race order accessed but not instantiated"); return null; @@ -45,26 +45,34 @@ public class MarkOrder { } /** - * Returns the mark in the race after the previous mark - * @param position The current race position - * @return the next race position - * OR null if there is no position + * @param seqID The seqID of the current mark the boat is heading to + * @return A Boolean indicating if this coming mark is the last one (finish line) */ - public RacePosition getNextPosition(RacePosition position){ - Mark previousMark = position.getNextMark(); - Mark nextMark; + public Boolean isLastMark(Integer seqID) { + return seqID == raceMarkOrder.size() - 1; + } - if (position.getPositionIndex() + 1 >= raceMarkOrder.size() - 1){ - RacePosition nextRacePosition = new RacePosition(raceMarkOrder.size() - 1, null, previousMark); - nextRacePosition.setFinishingLeg(); + /** + * @param currentSeqID The seqID of the current mark the boat is heading to + * @return The mark last passed + * @throws IndexOutOfBoundsException if there is no next mark. Check seqID != 0 first + */ + public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException { + return raceMarkOrder.get(currentSeqID - 1); + } - return nextRacePosition; - } + public CompoundMark getCurrentMark(Integer currentSeqID) { + return raceMarkOrder.get(currentSeqID); + } - Integer nextPositionIndex = position.getPositionIndex() + 1; - RacePosition nextRacePosition = new RacePosition(nextPositionIndex, raceMarkOrder.get(nextPositionIndex), previousMark); - - return nextRacePosition; + /** + * @param currentSeqID The seqID of the current mark the boat is heading to + * @return The mark following the mark that the boat is heading to + * @throws IndexOutOfBoundsException if there is no next mark. Check using {@link + * #isLastMark(Integer)} + */ + public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException { + return raceMarkOrder.get(currentSeqID + 1); } /** @@ -72,7 +80,7 @@ public class MarkOrder { * @param xml An AC35 RaceXML * @return An ordered list of marks in the race */ - private List loadRaceOrderFromXML(String xml){ + private List loadRaceOrderFromXML(String xml) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db; @@ -92,11 +100,11 @@ public class MarkOrder { logger.debug("Loaded RaceXML for mark order"); List corners = data.getMarkSequence(); Map marks = data.getCompoundMarks(); - List course = new ArrayList<>(); + List course = new ArrayList<>(); for (Corner corner : corners){ CompoundMark compoundMark = marks.get(corner.getCompoundMarkID()); - course.add(compoundMark.getMarks().get(0)); + course.add(compoundMark); } return course; @@ -105,17 +113,6 @@ public class MarkOrder { return null; } - /** - * @return The first position in the race - */ - public RacePosition getFirstPosition(){ - if (raceMarkOrder.size() > 0){ - return new RacePosition(-1, raceMarkOrder.get(0), null); - } - - return null; - } - /** * Load the raceXML and mark order */ @@ -132,4 +129,4 @@ public class MarkOrder { } raceMarkOrder = loadRaceOrderFromXML(raceXML); } -} +} \ No newline at end of file diff --git a/src/main/java/seng302/model/mark/RacePosition.java b/src/main/java/seng302/model/mark/RacePosition.java deleted file mode 100644 index fc160b10..00000000 --- a/src/main/java/seng302/model/mark/RacePosition.java +++ /dev/null @@ -1,55 +0,0 @@ -package seng302.model.mark; - -/** - * Represents a boats position between two marks - */ -public class RacePosition { - private Integer positionIndex; - private Mark nextMark; - private Mark previousMark; - private Boolean isFinishingLeg; - - public RacePosition(Integer positionIndex, Mark nextMark, Mark previousMark){ - this.positionIndex = positionIndex; - this.nextMark = nextMark; - this.previousMark = previousMark; - isFinishingLeg = false; - } - - /** - * @return The position of the boat (0...number of marks in race - 1) - */ - public Integer getPositionIndex(){ - return positionIndex; - } - - /** - * @return The mark the boat is heading to - * will return NULL if this is the finishing legg - */ - public Mark getNextMark(){ - return nextMark; - } - - /** - * @return The mark the boat is heading away from, - * Will return NULL if this is the starting leg - */ - public Mark getPreviousMark(){ - return previousMark; - } - - /** - * Sets a flag that this is the last leg in the race - */ - public void setFinishingLeg(){ - isFinishingLeg = true; - } - - /** - * @return true if this is the last leg in the race - */ - public boolean getIsFinishingLeg() { - return isFinishingLeg; - } -} diff --git a/src/main/java/seng302/model/stream/packets/PacketType.java b/src/main/java/seng302/model/stream/packets/PacketType.java index e7f55b49..1a38ae14 100644 --- a/src/main/java/seng302/model/stream/packets/PacketType.java +++ b/src/main/java/seng302/model/stream/packets/PacketType.java @@ -19,7 +19,9 @@ public enum PacketType { COURSE_WIND, AVG_WIND, BOAT_ACTION, - OTHER; + OTHER, + RACE_REGISTRATION_REQUEST, + RACE_REGISTRATION_RESPONSE; public static PacketType assignPacketType(int packetType, byte[] payload){ switch(packetType){ @@ -56,6 +58,10 @@ public enum PacketType { return AVG_WIND; case 100: return BOAT_ACTION; + case 101: + return RACE_REGISTRATION_REQUEST; + case 102: + return RACE_REGISTRATION_RESPONSE; default: } return OTHER; diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 9aa61b9d..f6968601 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -45,6 +45,19 @@ public class GeoUtility { return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0; } + + /** + * WARNING: this function DOES NOT account for wrapping around on lats / longs etc. + * SO BE CAREFUL IN USING THIS FUNCTION + * + * @param p1 GeoPoint 1 + * @param p2 GeoPoint 2 + * @return GeoPoint midPoint + */ + public static GeoPoint getDirtyMidPoint(GeoPoint p1, GeoPoint p2) { + return new GeoPoint((p1.getLat() + p2.getLat()) / 2, (p1.getLng() + p2.getLng()) / 2); + } + /** * Calculates the angle between to angular co-ordinates on a sphere in radians. * @@ -93,7 +106,6 @@ public class GeoUtility { return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng)); } - /** * Performs the line function on two points of a line and a test point to test which side of the * line that point is on. If the return value is return 1, then the point is on one side of the @@ -125,6 +137,31 @@ public class GeoUtility { } } + /** + * Checks if the line formed by lastLocation and location doesn't intersect the line segment + * formed by mark1 and mark2 See the wiki Mark Rounding algorithm for more info + * + * @param mark1 One mark of the line + * @param mark2 The second mark of the line + * @param lastLocation The last location of the point crossing this line + * @param location The current location of the point crossing this line + * @return 0 if two line segment doesn't intersect, otherwise 1 if they intersect and + * lastLocation is on RHS of the line segment (mark1 to mark2) or 2 if lastLocation on LHS of + * the line segment (mark1 to mark2) + */ + public static Integer checkCrossedLine(GeoPoint mark1, GeoPoint mark2, GeoPoint lastLocation, + GeoPoint location) { + boolean enteredDirection = isClockwise(mark1, mark2, lastLocation); + boolean exitedDirection = isClockwise(mark1, mark2, location); + if (enteredDirection != exitedDirection) { + if (!isPointInTriangle(mark1, lastLocation, location, mark2) + && !isPointInTriangle(mark2, lastLocation, location, mark1)) { + + return enteredDirection ? 1 : 2; + } + } + return 0; + } /** * Given a point and a vector (angle and vector length) Will create a new point, that vector @@ -155,10 +192,24 @@ public class GeoUtility { * @param bearing2 the bearing of v2 * @return the difference of bearing from v1 to v2 */ - private static double getBearingDiff(double bearing1, double bearing2) { + private static Double getBearingDiff(double bearing1, double bearing2) { return ((360 - bearing1) + bearing2) % 360; } + /** + * Check if a geo point ins on the right hand side of the line segment, which + * formed by two geo points v1 to v2. (Algorithm: point is clockwise to the + * line if the bearing difference is less than 180 deg.) + * + * @param v1 one end of the line segment + * @param v2 another end of the line segment + * @param point the point to be tested + * @return true if the point is on the RHS of the line + */ + public static Boolean isClockwise(GeoPoint v1, GeoPoint v2, GeoPoint point) { + return getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180; + } + /** * Given three geo points to form a triangle, the method returns true if the fourth point is * inside the triangle @@ -169,15 +220,15 @@ public class GeoUtility { * @param point the point to be tested * @return true if the fourth point is inside the triangle */ - public static boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) { - // true, if diff of bearing from (v1->v2) to (v1->p) is less than 180 deg - boolean sideFlag = getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180; + public static Boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) { + // true, if diff of bearing from (v1 to v2) to (v1 to p) is less than 180 deg + boolean isCW = isClockwise(v1, v2, point); - if ((getBearingDiff(getBearing(v2, v3), getBearing(v2, point)) < 180) != sideFlag) { + if (isClockwise(v2, v3, point) != isCW) { return false; } - if ((getBearingDiff(getBearing(v3, v1), getBearing(v3, point)) < 180) != sideFlag) { + if (isClockwise(v3, v1, point) != isCW) { return false; } diff --git a/src/main/java/seng302/utilities/XMLParser.java b/src/main/java/seng302/utilities/XMLParser.java index c231535d..c4c4348b 100644 --- a/src/main/java/seng302/utilities/XMLParser.java +++ b/src/main/java/seng302/utilities/XMLParser.java @@ -256,9 +256,9 @@ public class XMLParser { if (cMarkNode.getNodeName().equals("CompoundMark")) { cMark = new CompoundMark( XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"), - XMLParser.getNodeAttributeString(cMarkNode, "Name") + XMLParser.getNodeAttributeString(cMarkNode, "Name"), + createMarks(cMarkNode) ); - cMark.addSubMarks(createMarks(cMarkNode)); allMarks.add(cMark); } } diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index 838c92b6..9f76d37c 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -7,17 +7,28 @@ import java.io.OutputStream; import java.net.Socket; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Queue; +import java.util.Timer; +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 seng302.model.stream.packets.StreamPacket; +import javafx.scene.control.ButtonType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import seng302.gameServer.server.messages.BoatAction; import seng302.gameServer.server.messages.BoatActionMessage; +import seng302.gameServer.server.messages.ClientType; import seng302.gameServer.server.messages.Message; +import seng302.gameServer.server.messages.RegistrationRequestMessage; +import seng302.gameServer.server.messages.RegistrationResponseStatus; +import seng302.model.stream.packets.PacketType; +import seng302.model.stream.packets.StreamPacket; /** * A class describing a single connection to a Server for the purposes of sending and receiving on @@ -49,9 +60,17 @@ public class ClientToServerThread implements Runnable { private Socket socket; private InputStream is; - private OutputStream os; - private int clientId; + private Logger logger = LoggerFactory.getLogger(ClientToServerThread.class); + + //Output stream + private OutputStream os; + private Timer upWindPacketTimer = new Timer(); + private Timer downWindPacketTimer = new Timer(); + private boolean upwindTimerFlag = false, downwindTimerFlag = false; + static public final int PACKET_SENDING_INTERVAL_MS = 100; + + private int clientId = -1; // private Boolean updateClient = true; private ByteArrayOutputStream crcBuffer; @@ -73,15 +92,8 @@ public class ClientToServerThread implements Runnable { socket = new Socket(ipAddress, portNumber); is = socket.getInputStream(); os = socket.getOutputStream(); - Integer allocatedID = threeWayHandshake(); - if (allocatedID != null) { - clientId = allocatedID; - clientLog("Successful handshake. Allocated ID: " + clientId, 1); - } else { - clientLog("Unsuccessful handshake", 1); - closeSocket(); - return; - } + + sendRegistrationRequest(); thread = new Thread(this); thread.start(); @@ -130,15 +142,22 @@ public class ClientToServerThread implements Runnable { if (streamPackets.size() > 0) { streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); } else { - streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); - for (ClientSocketListener csl : listeners) - csl.newPacket(); + if (PacketType.RACE_REGISTRATION_RESPONSE == PacketType.assignPacketType(type, payload)){ + processRegistrationResponse(new StreamPacket(type, payloadLength, timeStamp, payload)); + } + else { + if (clientId == -1) continue; // Do not continue if not registered + streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); + for (ClientSocketListener csl : listeners) + csl.newPacket(); + } } } else { clientLog("Packet has been dropped", 1); } } } catch (ByteReadException e) { + e.printStackTrace(); closeSocket(); Platform.runLater(() -> { Alert alert = new Alert(AlertType.ERROR); @@ -149,7 +168,6 @@ public class ClientToServerThread implements Runnable { clientLog(e.getMessage(), 1); return; } -// System.out.println("streamPackets = " + streamPackets.size()); } closeSocket(); clientLog("Closed connection to Server", 0); @@ -157,43 +175,127 @@ public class ClientToServerThread implements Runnable { /** - * Listens for an allocated sourceID and returns it to the server - * - * @return the sourceID allocated to us by the server + * Sends a request to the server asking for a source ID */ - private Integer threeWayHandshake() { - Integer ourSourceID = null; - while (true) { - try { - ourSourceID = is.read(); - } catch (IOException e) { - clientLog("Three way handshake failed", 1); - } - if (ourSourceID != null) { - try { - os.write(ourSourceID); - return ourSourceID; - } catch (IOException e) { - clientLog("Three way handshake failed", 1); - return null; - } - } + private void sendRegistrationRequest() { + RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER); + + try { + os.write(requestMessage.getBuffer()); + } catch (IOException e) { + logger.error("Could not send registration request. Exiting"); + System.exit(1); } } - /** - * Send the post-start race course information - * @param boatActionMessage The message to send + * Accepts a response to the registration request message, and updates the client OR quits + * @param packet The registration requests packet */ - public void sendBoatActionMessage(BoatActionMessage boatActionMessage) { - try { - os.write(boatActionMessage.getBuffer()); - } catch (IOException e) { - clientLog("Could not write to server", 1); + private void processRegistrationResponse(StreamPacket packet){ + int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 3)); + int statusCode = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 4,5)); + + RegistrationResponseStatus status = RegistrationResponseStatus.getResponseStatus(statusCode); + + if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){ + clientId = sourceId; + + return; + } + + logger.error("Server Denied Connection, Exiting"); + + final String alertErrorText; + + if (status.equals(RegistrationResponseStatus.FAILURE_FULL)){ + alertErrorText = "Server is full"; + } + else{ + alertErrorText = "Could not connect to server"; + } + + Platform.runLater(() -> { + new Alert(AlertType.ERROR, alertErrorText, ButtonType.OK).showAndWait(); + System.exit(1); + }); + } + + /** + * Sends packets for the given boat action. Special cases are: \n + * - DOWNWIND = Packets are sent every ClientToServerThread.PACKET_SENDING_INTERVAL_MS + * - UPWIND = Packets are sent every ClientToServerThread.PACKET_SENDING_INTERVAL_MS + * - MAINTAIN_HEADING = DOWNWIND and UPWIND packets stop being sent. + * @param actionType The boat action that will dictate packets sent. + */ + public void sendBoatAction(BoatAction actionType) { + switch (actionType) { + case MAINTAIN_HEADING: + if (upwindTimerFlag) { + cancelTimer(upWindPacketTimer); + upwindTimerFlag = false; + upWindPacketTimer = new Timer(); + } + if (downwindTimerFlag) { + cancelTimer(downWindPacketTimer); + downwindTimerFlag = false; + downWindPacketTimer = new Timer(); + } + break; + case DOWNWIND: + if (!downwindTimerFlag) { + downwindTimerFlag = true; + downWindPacketTimer.scheduleAtFixedRate( + new TimerTask() { + @Override + public void run() { + sendBoatAction(new BoatActionMessage(BoatAction.DOWNWIND)); + } + }, 0, PACKET_SENDING_INTERVAL_MS + ); + } + break; + case UPWIND: + if (!upwindTimerFlag) { + upwindTimerFlag = true; + upWindPacketTimer.scheduleAtFixedRate( + new TimerTask() { + @Override + public void run() { + sendBoatAction(new BoatActionMessage(BoatAction.UPWIND)); + } + }, 0, PACKET_SENDING_INTERVAL_MS + ); + } + break; + default: + sendBoatAction(new BoatActionMessage(actionType)); + break; } } + /** + * Cancels a packet sending timer. + * @param timer The timer to cancel. + */ + private void cancelTimer (Timer timer) { + timer.cancel(); + timer.purge(); + } + + /** + * Sends a boat action of the given message type. + * @param message The given message type. + */ + private void sendBoatAction(BoatActionMessage message) { + if (clientId != -1) { + try { + os.write(message.getBuffer()); + } catch (IOException e) { + clientLog("Could not write to server", 1); + } + } + } private void closeSocket() { try { @@ -247,11 +349,8 @@ public class ClientToServerThread implements Runnable { } } - public Thread getThread() { - return thread; - } - public int getClientId () { return clientId; } + } diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 67a18a4b..f6b1c032 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -13,6 +13,7 @@ import javafx.scene.Node; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Pane; import seng302.gameServer.MainServerThread; +import seng302.gameServer.server.messages.BoatAction; import seng302.model.RaceState; import seng302.model.Yacht; import seng302.model.stream.packets.StreamPacket; @@ -22,8 +23,6 @@ import seng302.model.stream.parser.PositionUpdateData.DeviceType; import seng302.model.stream.parser.RaceStatusData; import seng302.model.stream.xml.parser.RaceXMLData; import seng302.model.stream.xml.parser.RegattaXMLData; -import seng302.gameServer.server.messages.BoatActionMessage; -import seng302.gameServer.server.messages.BoatActionType; import seng302.utilities.StreamParser; import seng302.utilities.XMLParser; import seng302.visualiser.controllers.LobbyController; @@ -31,7 +30,8 @@ import seng302.visualiser.controllers.LobbyController.CloseStatus; import seng302.visualiser.controllers.RaceViewController; /** - * Created by cir27 on 20/07/17. + * This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated + * with a JavaFX Pane to insert itself into. */ public class GameClient { @@ -48,13 +48,20 @@ public class GameClient { private ObservableList clientLobbyList = FXCollections.observableArrayList(); - private long lastSendingTime; - private int KEY_STROKE_SENDING_FREQUENCY = 50; - + /** + * Create an instance of the game client. Does not do anything until run with runAsClient() + * runAsHost(). + * @param holder The JavaFX Pane that the visual elements for the race will be inserted into. + */ public GameClient(Pane holder) { this.holderPane = holder; } + /** + * Connect to a game at the given address and starts the visualiser. + * @param ipAddress IP to connect to. + * @param portNumber Port to connect to. + */ public void runAsClient(String ipAddress, Integer portNumber) { try { socketThread = new ClientToServerThread(ipAddress, portNumber); @@ -62,6 +69,7 @@ public class GameClient { ioe.printStackTrace(); System.out.println("Unable to connect to host..."); } + socketThread.addStreamObserver(this::parsePackets); LobbyController lobbyController = loadLobby(); lobbyController.setPlayerListSource(clientLobbyList); @@ -70,6 +78,11 @@ public class GameClient { lobbyController.addCloseListener((exitCause) -> this.loadStartScreen()); } + /** + * Connect to a game as the host at the given address and starts the visualiser. + * @param ipAddress IP to connect to. + * @param portNumber Port to connect to. + */ public void runAsHost(String ipAddress, Integer portNumber) { server = new MainServerThread(); try { @@ -93,10 +106,8 @@ public class GameClient { private void loadStartScreen() { socketThread.setSocketToClose(); - socketThread = null; if (server != null) { - // TODO: 26/07/17 cir27 - handle disconnecting -// server.shutDown(); + server.terminate(); server = null; } FXMLLoader fxmlLoader = new FXMLLoader( @@ -178,12 +189,9 @@ public class GameClient { StreamParser.extractXmlMessage(packet) ); clientLobbyList.clear(); - allBoatsMap.forEach((id, boat) -> { - clientLobbyList.add(id + " " + boat.getBoatName()); -// System.out.println(id + " " + boat.getBoatName()); - - }); -// startRaceIfAllDataReceived(); + allBoatsMap.forEach((id, boat) -> + clientLobbyList.add(id + " " + boat.getBoatName()) + ); break; case RACE_START_STATUS: @@ -282,28 +290,15 @@ public class GameClient { * @param e The key event triggering this call */ public void keyPressed(KeyEvent e) { - BoatActionMessage boatActionMessage; - long currentTime = System.currentTimeMillis(); - if (currentTime - lastSendingTime > KEY_STROKE_SENDING_FREQUENCY) { - lastSendingTime = currentTime; - switch (e.getCode()) { - case SPACE: // align with vmg - boatActionMessage = new BoatActionMessage(BoatActionType.VMG); - socketThread.sendBoatActionMessage(boatActionMessage); - break; - case PAGE_UP: // upwind - boatActionMessage = new BoatActionMessage(BoatActionType.UPWIND); - socketThread.sendBoatActionMessage(boatActionMessage); - break; - case PAGE_DOWN: // downwind - boatActionMessage = new BoatActionMessage(BoatActionType.DOWNWIND); - socketThread.sendBoatActionMessage(boatActionMessage); - break; - case ENTER: // tack/gybe - boatActionMessage = new BoatActionMessage(BoatActionType.TACK_GYBE); - socketThread.sendBoatActionMessage(boatActionMessage); - break; - //TODO Allow a zoom in and zoom out methods + switch (e.getCode()) { + case SPACE: // align with vmg + socketThread.sendBoatAction(BoatAction.VMG); break; + case PAGE_UP: // upwind + socketThread.sendBoatAction(BoatAction.UPWIND); break; + case PAGE_DOWN: // downwind + socketThread.sendBoatAction(BoatAction.DOWNWIND); break; + case ENTER: // tack/gybe + socketThread.sendBoatAction(BoatAction.TACK_GYBE); break; case Z: // zoom in raceView.getGameView().zoomIn(); break; @@ -314,15 +309,17 @@ public class GameClient { } } - public void keyReleased(KeyEvent e) { + private void keyReleased(KeyEvent e) { switch (e.getCode()) { //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet) case SHIFT: // sails in/sails out - BoatActionMessage boatActionMessage = new BoatActionMessage( - BoatActionType.SAILS_IN); - socketThread.sendBoatActionMessage(boatActionMessage); + socketThread.sendBoatAction(BoatAction.SAILS_IN); raceView.getGameView().getPlayerYacht().toggleClientSail(); break; + case PAGE_UP: + case PAGE_DOWN: + socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break; + } } } diff --git a/src/main/java/seng302/visualiser/GameView.java b/src/main/java/seng302/visualiser/GameView.java index 920939c2..70f2465d 100644 --- a/src/main/java/seng302/visualiser/GameView.java +++ b/src/main/java/seng302/visualiser/GameView.java @@ -168,7 +168,7 @@ public class GameView extends Pane { } } // Platform.runLater(() -> -// boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation()) + boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation()); // ); } }; @@ -523,7 +523,6 @@ public class GameView extends Pane { distanceFromReference = GeoUtility.getDistance( minLatPoint, new GeoPoint(unscaledLat, unscaledLon) ); -// System.out.println("distanceFromReference = " + distanceFromReference); if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) { xAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); yAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); @@ -543,8 +542,6 @@ public class GameView extends Pane { if(horizontalInversion) { xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize); } -// System.out.println("yAxisLocation = " + yAxisLocation + " " + unscaledLat); -// System.out.println("xAxisLocation = " + xAxisLocation + " " + unscaledLon); return new Point2D(xAxisLocation, yAxisLocation); } diff --git a/src/main/java/seng302/visualiser/controllers/LobbyController.java b/src/main/java/seng302/visualiser/controllers/LobbyController.java index 1a8b13e3..809bd3b5 100644 --- a/src/main/java/seng302/visualiser/controllers/LobbyController.java +++ b/src/main/java/seng302/visualiser/controllers/LobbyController.java @@ -4,15 +4,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import javafx.application.Platform; -import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.Button; -import javafx.scene.control.ListView; +import javafx.scene.control.TextArea; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.layout.GridPane; import javafx.scene.text.Text; import seng302.gameServer.GameStages; import seng302.gameServer.GameState; @@ -33,28 +31,26 @@ public class LobbyController { void notify(CloseStatus exitCause); } - @FXML - private GridPane lobbyScreen; @FXML private Text lobbyIpText; @FXML private Button readyButton; @FXML - private ListView firstListView; + private TextArea playerOneTxt; @FXML - private ListView secondListView; + private TextArea playerTwoTxt; @FXML - private ListView thirdListView; + private TextArea playerThreeTxt; @FXML - private ListView fourthListView; + private TextArea playerFourTxt; @FXML - private ListView fifthListView; + private TextArea playerFiveTxt; @FXML - private ListView sixthListView; + private TextArea playerSixTxt; @FXML - private ListView seventhListView; + private TextArea playerSevenTxt; @FXML - private ListView eighthListView; + private TextArea playerEightTxt; @FXML private ImageView firstImageView; @FXML @@ -72,79 +68,67 @@ public class LobbyController { @FXML private ImageView eighthImageView; - private List> competitors = new ArrayList<>(); - private ObservableList firstCompetitor = FXCollections.observableArrayList(); - private ObservableList secondCompetitor = FXCollections.observableArrayList(); - private ObservableList thirdCompetitor = FXCollections.observableArrayList(); - private ObservableList fourthCompetitor = FXCollections.observableArrayList(); - private ObservableList fifthCompetitor = FXCollections.observableArrayList(); - private ObservableList sixthCompetitor = FXCollections.observableArrayList(); - private ObservableList seventhCompetitor = FXCollections.observableArrayList(); - private ObservableList eighthCompetitor = FXCollections.observableArrayList(); - private List imageViews = new ArrayList<>(); - private List listViews; + private List