diff --git a/.gitignore b/.gitignore index 3c15f041..213db0db 100644 --- a/.gitignore +++ b/.gitignore @@ -180,3 +180,7 @@ local.properties .recommenders/ Makefile + +infer-out/ +infer.txt +log.log \ No newline at end of file diff --git a/doc/design_decisions.md b/doc/design_decisions.md index 65e68153..0c1f6f00 100644 --- a/doc/design_decisions.md +++ b/doc/design_decisions.md @@ -9,7 +9,7 @@ prints out event details, including time, involved boats and legs. - Configuration file -We decided to store the team information including team names and boat velocity, as well as race configuration setting in external file. +We decided to store the team information including team names and boat currentVelocity, as well as race configuration setting in external file. To read external files, "Json-simple" library has been used to parse information. By using this library, we did not have to write our json parser and benefited from the flexibility of json files. diff --git a/doc/user_manual.md b/doc/user_manual.md index 0ca79deb..27f09fc3 100644 --- a/doc/user_manual.md +++ b/doc/user_manual.md @@ -8,7 +8,7 @@ You can specify a config file using the using the -f flag, for example 'java -ja ## The config file -The teams/boats are specified in the config file under 'teams', each team requires a team name, and a velocity (in meters per second). +The teams/boats are specified in the config file under 'teams', each team requires a team name, and a currentVelocity (in meters per second). The 'time-scale' option lets you change how long the race takes to complete. A time-scale of 1.0 is normal speed, 2.0 is 2x etc. diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 9a54c7d1..a6716807 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,26 +1,51 @@ package seng302.gameServer; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import seng302.gameServer.server.messages.BoatAction; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import seng302.gameServer.server.messages.BoatAction; +import seng302.gameServer.server.messages.BoatStatus; +import seng302.gameServer.server.messages.MarkRoundingMessage; +import seng302.gameServer.server.messages.MarkType; +import seng302.gameServer.server.messages.Message; +import seng302.gameServer.server.messages.RoundingBoatStatus; +import seng302.gameServer.server.messages.YachtEventCodeMessage; +import seng302.model.GeoPoint; import seng302.model.Player; -import seng302.model.Yacht; +import seng302.model.PolarTable; +import seng302.model.ServerYacht; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.Mark; import seng302.model.mark.MarkOrder; +import seng302.utilities.GeoUtility; /** * A Static class to hold information about the current state of the game (model) + * Also contains logic for updating itself on regular time intervals on its own thread * Created by wmu16 on 10/07/17. */ public class GameState implements Runnable { - private Logger logger = LoggerFactory.getLogger(MarkOrder.class); + @FunctionalInterface + interface NewMessageListener { + void notify(Message message); + } - private static Integer STATE_UPDATES_PER_SECOND = 60; + private Logger logger = LoggerFactory.getLogger(GameState.class); + + private static final Integer STATE_UPDATES_PER_SECOND = 60; public static Integer MAX_PLAYERS = 8; + public static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further + public static final Double MARK_COLLISION_DISTANCE = 15d; + public static final Double YACHT_COLLISION_DISTANCE = 25.0; + public static final Double BOUNCE_DISTANCE_MARK = 20.0; + public static final Double BOUNCE_DISTANCE_YACHT = 30.0; + public static final Double COLLISION_VELOCITY_PENALTY = 0.3; private static Long previousUpdateTime; public static Double windDirection; @@ -28,11 +53,14 @@ public class GameState implements Runnable { private static String hostIpAddress; private static List players; - private static Map yachts; + private static Map yachts; private static Boolean isRaceStarted; private static GameStages currentStage; private static MarkOrder markOrder; private static long startTime; + private static Set marks; + + private static List markListeners; private static Map playerStringMap = new HashMap<>(); /* @@ -53,21 +81,27 @@ public class GameState implements Runnable { yachts = new HashMap<>(); players = new ArrayList<>(); GameState.hostIpAddress = hostIpAddress; - players = new ArrayList<>(); + ; currentStage = GameStages.LOBBYING; isRaceStarted = false; //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? + markListeners = new ArrayList<>(); - new Thread(this).start(); //Run the auto updates on the game state + new Thread(this, "GameState").start(); //Run the auto updates on the game state + + marks = new MarkOrder().getAllMarks(); } public static String getHostIpAddress() { return hostIpAddress; } + public static Set getMarks(){ + return Collections.unmodifiableSet(marks); + } + public static List getPlayers() { return players; } @@ -78,13 +112,13 @@ public class GameState implements Runnable { + " " + player.getYacht().getCountry(); playerStringMap.put(player, playerText); } - + public static void removePlayer(Player player) { players.remove(player); playerStringMap.remove(player); } - public static void addYacht(Integer sourceId, Yacht yacht) { + public static void addYacht(Integer sourceId, ServerYacht yacht) { yachts.put(sourceId, yacht); } @@ -101,7 +135,7 @@ public class GameState implements Runnable { } public static void setCurrentStage(GameStages currentStage) { - if (currentStage == GameStages.RACING){ + if (currentStage == GameStages.RACING) { startTime = System.currentTimeMillis(); } @@ -125,56 +159,17 @@ public class GameState implements Runnable { } public static Double getWindSpeedKnots() { - return windSpeed / 1000 * 1.943844492; // TODO: 26/07/17 cir27 - remove magic numbers + return GeoUtility.mmsToKnots(windSpeed); // TODO: 26/07/17 cir27 - remove magic numbers } - public static Map getYachts() { + public static Map getYachts() { return yachts; } - public static void updateBoat(Integer sourceId, BoatAction actionType) { - Yacht playerYacht = yachts.get(sourceId); -// System.out.println("-----------------------"); - switch (actionType) { - case VMG: - playerYacht.turnToVMG(); -// System.out.println("Snapping to VMG"); - 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; - } - -// printBoatStatus(playerYacht); - } - - public void update() { - Long timeInterval = System.currentTimeMillis() - previousUpdateTime; - previousUpdateTime = System.currentTimeMillis(); - for (Yacht yacht : yachts.values()) { - yacht.update(timeInterval); - } - } /** * Generates a new ID based off the size of current players + 1 + * * @return a playerID to be allocated to a new connetion */ public static Integer getUniquePlayerID() { @@ -189,7 +184,7 @@ public class GameState implements Runnable { @Override public void run() { - while(true) { + while (currentStage != GameStages.FINISHED) { try { Thread.sleep(1000 / STATE_UPDATES_PER_SECOND); } catch (InterruptedException e) { @@ -199,20 +194,395 @@ public class GameState implements Runnable { update(); } - //RACING if (currentStage == GameStages.RACING) { update(); } } } - private static void printBoatStatus(Yacht playerYacht) { - System.out.println("-----------------------"); - System.out.println("Sails are in: " + playerYacht.getSailIn()); - System.out.println("Heading: " + playerYacht.getHeading()); - System.out.println("Velocity: " + playerYacht.getVelocityMMS() / 1000); - System.out.println("Lat: " + playerYacht.getLocation().getLat()); - System.out.println("Lng: " + playerYacht.getLocation().getLng()); - System.out.println("-----------------------\n"); + public static void updateBoat(Integer sourceId, BoatAction actionType) { + ServerYacht playerYacht = yachts.get(sourceId); + switch (actionType) { + case VMG: + playerYacht.turnToVMG(); + break; + case SAILS_IN: + playerYacht.toggleSailIn(); + break; + case SAILS_OUT: + playerYacht.toggleSailIn(); + break; + case TACK_GYBE: + playerYacht.tackGybe(windDirection); + break; + case UPWIND: + playerYacht.turnUpwind(); + break; + case DOWNWIND: + playerYacht.turnDownwind(); + break; + } + } + + + /** + * Called periodically in this GameState thread to update the GameState values + */ + public void update() { + Boolean raceFinished = true; + + Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0; + previousUpdateTime = System.currentTimeMillis(); + for (ServerYacht yacht : yachts.values()) { + updateVelocity(yacht); + yacht.runAutoPilot(); + yacht.updateLocation(timeInterval); + if (yacht.getBoatStatus() != BoatStatus.FINISHED) { + checkForCollision(yacht); + checkForLegProgression(yacht); + raceFinished = false; + } + } + + if (raceFinished) { + currentStage = GameStages.FINISHED; + } + } + + + public static void checkForCollision(ServerYacht serverYacht) { + ServerYacht collidedYacht = checkCollision(serverYacht); + if (collidedYacht != null) { + GeoPoint originalLocation = serverYacht.getLocation(); + serverYacht.setLocation( + calculateBounceBack(serverYacht, originalLocation, BOUNCE_DISTANCE_YACHT) + ); + serverYacht.setCurrentVelocity( + serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY + ); + collidedYacht.setLocation( + calculateBounceBack(collidedYacht, originalLocation, BOUNCE_DISTANCE_YACHT) + ); + collidedYacht.setCurrentVelocity( + collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY + ); + notifyMessageListeners( + new YachtEventCodeMessage(serverYacht.getSourceId()) + ); + } else { + Mark collidedMark = markCollidedWith(serverYacht); + if (collidedMark != null) { + serverYacht.setLocation( + calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK) + ); + serverYacht.setCurrentVelocity( + serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY + ); + notifyMessageListeners( + new YachtEventCodeMessage(serverYacht.getSourceId()) + ); + } + } + } + + + private void updateVelocity(ServerYacht yacht) { + Double velocity = yacht.getCurrentVelocity(); + Double trueWindAngle = Math.abs(windDirection - yacht.getHeading()); + Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle); + Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots); + // TODO: 15/08/17 remove magic numbers from these equations. + if (yacht.getSailIn()) { + if (velocity < maxBoatSpeed - 500) { + yacht.changeVelocity(maxBoatSpeed / 100); + } else if (velocity > maxBoatSpeed + 500) { + yacht.changeVelocity(-maxBoatSpeed / 100); + } else { + yacht.setCurrentVelocity(maxBoatSpeed); + } + } else { + if (velocity > 3000) { + yacht.changeVelocity(-velocity / 200); + } else if (velocity > 100) { + yacht.changeVelocity(-velocity / 50); + } else if (velocity <= 100){ + yacht.setCurrentVelocity(0d); + } + } + } + + + /** + * 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)} + */ + private Double calcDistanceToCurrentMark(ServerYacht yacht) throws IndexOutOfBoundsException { + Integer currentMarkSeqID = yacht.getCurrentMarkSeqID(); + CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID); + GeoPoint location = yacht.getLocation(); + + if (currentMark.isGate()) { + Mark sub1 = currentMark.getSubMark(1); + Mark sub2 = currentMark.getSubMark(2); + Double distance1 = GeoUtility.getDistance(location, sub1); + Double distance2 = GeoUtility.getDistance(location, sub2); + if (distance1 < distance2) { + yacht.setClosestCurrentMark(sub1); + return distance1; + } else { + yacht.setClosestCurrentMark(sub2); + return distance2; + } + } else { + yacht.setClosestCurrentMark(currentMark.getSubMark(1)); + return GeoUtility.getDistance(location, currentMark.getSubMark(1)); + } + } + + + /** + * 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 + * @param yacht the current yacht to check for progression + */ + private void checkForLegProgression(ServerYacht yacht) { + Integer currentMarkSeqID = yacht.getCurrentMarkSeqID(); + CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID); +// System.out.println(yacht.getCurrentMarkSeqID()); + + Boolean hasProgressed; + if (currentMarkSeqID == 0) { + hasProgressed = checkStartLineCrossing(yacht); + } else if (markOrder.isLastMark(currentMarkSeqID)) { + hasProgressed = checkFinishLineCrossing(yacht); + } else if (currentMark.isGate()) { + hasProgressed = checkGateRounding(yacht); + } else { + hasProgressed = checkMarkRounding(yacht); + } + + if (hasProgressed) { + sendMarkRoundingMessage(yacht); + logMarkRounding(yacht); + yacht.setHasPassedLine(false); + yacht.setHasEnteredRoundingZone(false); + yacht.setHasPassedThroughGate(false); + if (!markOrder.isLastMark(currentMarkSeqID)) { + yacht.incrementMarkSeqID(); + } + } + } + + + /** + * If we pass the start line gate in the correct direction, progress + * + * @param yacht The current yacht to check for + */ + private Boolean checkStartLineCrossing(ServerYacht yacht) { + Integer currentMarkSeqID = yacht.getCurrentMarkSeqID(); + CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID); + GeoPoint lastLocation = yacht.getLastLocation(); + GeoPoint location = yacht.getLocation(); + + Mark mark1 = currentMark.getSubMark(1); + Mark mark2 = currentMark.getSubMark(2); + CompoundMark nextMark = markOrder.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) { + yacht.setClosestCurrentMark(mark1); + yacht.setBoatStatus(BoatStatus.RACING); + return true; + } + } + + return false; + } + + + /** + * 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' + * + * @param yacht The current yacht to check for + */ + private Boolean checkMarkRounding(ServerYacht yacht) { + Integer currentMarkSeqID = yacht.getCurrentMarkSeqID(); + CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID); + GeoPoint lastLocation = yacht.getLastLocation(); + GeoPoint location = yacht.getLocation(); + GeoPoint nextPoint = markOrder.getNextMark(currentMarkSeqID).getMidPoint(); + GeoPoint prevPoint = markOrder.getPreviousMark(currentMarkSeqID).getMidPoint(); + GeoPoint midPoint = GeoUtility.getDirtyMidPoint(nextPoint, prevPoint); + + if (calcDistanceToCurrentMark(yacht) < ROUNDING_DISTANCE) { + yacht.setHasEnteredRoundingZone(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)) { + yacht.setHasPassedLine(true); + } + } + + return yacht.hasPassedLine() && yacht.hasEnteredRoundingZone(); + } + + + /** + * Checks if a gate line has been crossed and in the correct direction + * + * @param yacht The current yacht to check for + */ + private Boolean checkGateRounding(ServerYacht yacht) { + Integer currentMarkSeqID = yacht.getCurrentMarkSeqID(); + CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID); + GeoPoint lastLocation = yacht.getLastLocation(); + GeoPoint location = yacht.getLocation(); + + Mark mark1 = currentMark.getSubMark(1); + Mark mark2 = currentMark.getSubMark(2); + CompoundMark prevMark = markOrder.getPreviousMark(currentMarkSeqID); + CompoundMark nextMark = markOrder.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) { + yacht.setHasPassedThroughGate(true); + } + } + + Boolean prevMarkSide = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint()); + Boolean nextMarkSide = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint()); + + if (yacht.hasPassedThroughGate()) { + //Check if we need to round this gate after passing through + if (prevMarkSide == nextMarkSide) { + return checkMarkRounding(yacht); + } else { + return true; + } + } + + return false; + } + + /** + * If we pass the finish gate in the correct direction + * + * @param yacht The current yacht to check for + */ + private Boolean checkFinishLineCrossing(ServerYacht yacht) { + Integer currentMarkSeqID = yacht.getCurrentMarkSeqID(); + CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID); + GeoPoint lastLocation = yacht.getLastLocation(); + GeoPoint location = yacht.getLocation(); + + Mark mark1 = currentMark.getSubMark(1); + Mark mark2 = currentMark.getSubMark(2); + CompoundMark prevMark = markOrder.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) { + yacht.setClosestCurrentMark(mark1); + yacht.setBoatStatus(BoatStatus.FINISHED); + return true; + } + } + + return false; + } + + + private static Mark markCollidedWith(ServerYacht yacht) { + Set marksInRace = GameState.getMarks(); + for (Mark mark : marksInRace) { + if (GeoUtility.getDistance(yacht.getLocation(), mark) + <= MARK_COLLISION_DISTANCE) { + return mark; + } + } + return null; + } + + /** + * Calculate the new position of the boat after it has had a collision + * + * @return The boats new position + */ + private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith, Double bounceDistance) { + Double heading = GeoUtility.getBearing(yacht.getLocation(), collidedWith); + // Invert heading + heading -= 180; + Integer newHeading = Math.floorMod(heading.intValue(), 360); + return GeoUtility.getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance); + } + + /** + * Collision detection which iterates through all the yachts and check if any yacht collided + * with this yacht. Return collided yacht or null if no collision. + * + * @return yacht to compare to all other yachts. + */ + private static ServerYacht checkCollision(ServerYacht yacht) { + + for (ServerYacht otherYacht : GameState.getYachts().values()) { + if (otherYacht != yacht) { + Double distance = GeoUtility.getDistance(otherYacht.getLocation(), yacht.getLocation()); + if (distance < YACHT_COLLISION_DISTANCE) { + return otherYacht; + } + } + } + return null; + } + + private void sendMarkRoundingMessage(ServerYacht yacht) { + Integer sourceID = yacht.getSourceId(); + Integer currentMarkSeqID = yacht.getCurrentMarkSeqID(); + CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID); + MarkType markType = (currentMark.isGate()) ? MarkType.GATE : MarkType.ROUNDING_MARK; + Mark roundingMark = yacht.getClosestCurrentMark(); + + // TODO: 13/8/17 figure out the rounding side, rounded mark source ID and boat status. + Message markRoundingMessage = new MarkRoundingMessage(0, 0, + sourceID, RoundingBoatStatus.RACING, roundingMark.getRoundingSide(), markType, + roundingMark.getSourceID()); + + notifyMessageListeners(markRoundingMessage); + } + + private static void notifyMessageListeners(Message message) { + for (NewMessageListener mpl : markListeners) { + mpl.notify(message); + } + } + + private void logMarkRounding(ServerYacht yacht) { + Mark roundingMark = yacht.getClosestCurrentMark(); + logger.debug( + String.format("Yacht srcID(%d) passed Mark srcID(%d)", yacht.getSourceId(), + roundingMark.getSourceID())); + } + + + public static void addMarkPassListener(NewMessageListener listener) { + markListeners.add(listener); } } diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index 37d693ca..bd22063c 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -1,20 +1,31 @@ package seng302.gameServer; +import gherkin.lexer.Fi; import java.io.IOException; import java.net.ServerSocket; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Observable; +import java.util.List; import java.util.Timer; import java.util.TimerTask; +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.model.GeoPoint; import seng302.model.Player; import seng302.model.PolarTable; +import seng302.model.ServerYacht; +import seng302.model.mark.CompoundMark; +import seng302.utilities.GeoUtility; +import seng302.visualiser.GameClient; /** * 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 Observable implements Runnable, ClientConnectionDelegate{ +public class MainServerThread implements Runnable, ClientConnectionDelegate { private static final int PORT = 4942; private static final Integer CLIENT_UPDATES_PER_SECOND = 10; @@ -26,6 +37,8 @@ public class MainServerThread extends Observable implements Runnable, ClientConn private ServerSocket serverSocket = null; private ArrayList serverToClientThreads = new ArrayList<>(); + private GameClient gameClient; + public MainServerThread() { new GameState("localhost"); try { @@ -34,8 +47,9 @@ public class MainServerThread extends Observable implements Runnable, ClientConn serverLog("IO error in server thread handler upon trying to make new server socket", 0); } PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); + GameState.addMarkPassListener(this::broadcastMessage); terminated = false; - thread = new Thread(this); + thread = new Thread(this, "MainServer"); thread.start(); } @@ -45,7 +59,6 @@ public class MainServerThread extends Observable implements Runnable, ClientConn HeartbeatThread heartbeatThread; serverListenThread = new ServerListenThread(serverSocket, this); - heartbeatThread = new HeartbeatThread(this); heartbeatThread.start(); @@ -70,7 +83,7 @@ public class MainServerThread extends Observable implements Runnable, ClientConn //FINISHED else if (GameState.getCurrentStage() == GameStages.FINISHED) { - + terminate(); } } @@ -85,19 +98,27 @@ public class MainServerThread extends Observable implements Runnable, ClientConn public void updateClients() { for (ServerToClientThread serverToClientThread : serverToClientThreads) { - serverToClientThread.updateClient(); + serverToClientThread.sendBoatLocationPackets(); + } + } + + private void broadcastMessage(Message message) { + for (ServerToClientThread serverToClientThread : serverToClientThreads) { + serverToClientThread.sendMessage(message); } } - static void serverLog(String message, int logLevel){ - if(logLevel <= LOG_LEVEL){ - System.out.println("[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message); + static void serverLog(String message, int logLevel) { + if (logLevel <= LOG_LEVEL) { + System.out.println( + "[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message); } } /** * A client has tried to connect to the server + * * @param serverToClientThread The player that connected */ @Override @@ -113,6 +134,7 @@ public class MainServerThread extends Observable implements Runnable, ClientConn /** * A player has left the game, remove the player from the GameState + * * @param player The player that left */ @Override @@ -134,25 +156,99 @@ public class MainServerThread extends Observable implements Runnable, ClientConn } } serverToClientThreads.remove(closedConnection); - setChanged(); - notifyObservers(); } public void startGame() { + initialiseBoatPositions(); Timer t = new Timer(); t.schedule(new TimerTask() { @Override public void run() { - - for (ServerToClientThread serverToClientThread : serverToClientThreads) { - serverToClientThread.sendRaceStatusMessage(); - } + broadcastMessage(makeRaceStatusMessage()); } }, 0, 500); } + private RaceStatusMessage makeRaceStatusMessage() { + // variables taken from GameServerThread + + List boatSubMessages = new ArrayList<>(); + RaceStatus raceStatus; + + for (Player player : GameState.getPlayers()) { + ServerYacht y = player.getYacht(); + BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(), 0, + 0, 0, 1234L, + 1234L); + boatSubMessages.add(m); + } + + if (GameState.getCurrentStage() == GameStages.RACING) { + raceStatus = RaceStatus.STARTED; + } else { + raceStatus = RaceStatus.WARNING; + } + + return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(), + GameState.getWindDirection(), + GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(), + RaceType.MATCH_RACE, 1, boatSubMessages); + } + public void terminate() { terminated = true; } + + /** + * Pass GameClient to main server thread so it can access the properties inside. + * + * @param gameClient gameClient + */ + public void setGameClient(GameClient gameClient) { + this.gameClient = gameClient; + } + + /** + * Initialise boats to specific spaced out geopoints behind starting line. + */ + private void initialiseBoatPositions() { + // Getting the start line compound marks + CompoundMark cm = gameClient.getCourseData().getCompoundMarks().get(1); + GeoPoint startMark1 = new GeoPoint(cm.getMarks().get(0).getLat(), + cm.getMarks().get(0).getLng()); + GeoPoint startMark2 = new GeoPoint(cm.getMarks().get(1).getLat(), + cm.getMarks().get(1).getLng()); + + // Calculating midpoint + Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2); + Double length = GeoUtility.getDistance(startMark1, startMark2); + GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2); + + // Setting each boats position side by side + double DISTANCE_FACTOR = 50.0; // distance apart in meters + int boatIndex = 0; + for (ServerYacht yacht : GameState.getYachts().values()) { + int distanceApart = boatIndex / 2; + + if (boatIndex % 2 == 1 && boatIndex != 0) { + distanceApart++; + distanceApart *= -1; + } + + GeoPoint spawnMark = GeoUtility + .getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * DISTANCE_FACTOR); + + if (yacht.getHeading() < perpendicularAngle) { + spawnMark = GeoUtility + .getGeoCoordinate(spawnMark, perpendicularAngle + 90, DISTANCE_FACTOR); + } else { + spawnMark = GeoUtility + .getGeoCoordinate(spawnMark, perpendicularAngle + 270, DISTANCE_FACTOR); + } + + yacht.setLocation(spawnMark); + boatIndex++; + } + } } diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index a3f87e2b..bae4216c 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -9,16 +9,25 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; import java.net.SocketException; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Observable; +import java.util.Observer; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.zip.CRC32; import java.util.zip.Checksum; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import seng302.gameServer.server.messages.YachtEventCodeMessage; +import seng302.model.Player; +import seng302.model.stream.packets.PacketType; +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.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; @@ -29,20 +38,14 @@ 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; -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.model.ServerYacht; /** * 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 { +public class ServerToClientThread implements Runnable, Observer { /** * Called to notify listeners when this thread receives a connection correctly. @@ -52,8 +55,7 @@ public class ServerToClientThread implements Runnable { void notifyConnection (); } - private static final Integer LOG_LEVEL = 1; - private static final Integer MAX_ID_ATTEMPTS = 10; + private Logger logger = LoggerFactory.getLogger(ServerToClientThread.class); private Thread thread; @@ -63,8 +65,6 @@ public class ServerToClientThread implements Runnable { private ByteArrayOutputStream crcBuffer; -// private Boolean initialisedRace = true; - private Integer seqNo; private Integer sourceId; @@ -75,6 +75,8 @@ public class ServerToClientThread implements Runnable { private List connectionListeners = new ArrayList<>(); + private ServerYacht yacht; + public ServerToClientThread(Socket socket) { this.socket = socket; seqNo = 0; @@ -86,7 +88,7 @@ public class ServerToClientThread implements Runnable { return; } - thread = new Thread(this); + thread = new Thread(this, "ServerToClient"); thread.start(); } @@ -115,18 +117,21 @@ public class ServerToClientThread implements Runnable { all = ln.lines().collect(Collectors.toList()); lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); - - Yacht yacht = new Yacht( + ServerYacht yacht = new ServerYacht( "Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" ); + + yacht.addObserver(this); // TODO: yacht can notify mark rounding message hyi25 13/8/17 GameState.addYacht(sourceId, yacht); GameState.addPlayer(new Player(socket, yacht)); } - static void serverLog(String message, int logLevel) { - if (logLevel <= LOG_LEVEL) { - System.out.println( - "[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message); + @Override + public void update(Observable o, Object arg) { + if (arg != null) { + sendMessage((Message) arg); + } else { + sendSetupMessages(); } } @@ -164,7 +169,6 @@ public class ServerToClientThread implements Runnable { int sync2; // TODO: 14/07/17 wmu16 - Work out how to fix this while loop - while (socket.isConnected()) { try { @@ -201,7 +205,7 @@ public class ServerToClientThread implements Runnable { break; } } else { - serverLog("Packet has been dropped", 1); + logger.warn("Packet has been dropped", 1); } } } catch (Exception e) { @@ -217,7 +221,7 @@ public class ServerToClientThread implements Runnable { xml = new XMLGenerator(); Race race = new Race(); - for (Yacht yacht : GameState.getYachts().values()) { + for (ServerYacht yacht : GameState.getYachts().values()) { race.addBoat(yacht); } @@ -239,10 +243,6 @@ public class ServerToClientThread implements Runnable { sendMessage(xmlMessage); } - public void updateClient() { - sendBoatLocationPackets(); - } - private void closeSocket() { try { socket.close(); @@ -259,7 +259,7 @@ public class ServerToClientThread implements Runnable { crcBuffer.write(currentByte); } catch (IOException e) { e.printStackTrace(); - serverLog("Socket read failed", 1); + logger.warn("Socket read failed", 1); } if (currentByte == -1) { throw new Exception(); @@ -288,7 +288,7 @@ public class ServerToClientThread implements Runnable { //serverLog("Player " + sourceId + " side socket disconnected", 1); return; } catch (IOException e) { - serverLog("Message send failed", 1); + logger.warn("Message send failed", 1); } } @@ -298,9 +298,9 @@ public class ServerToClientThread implements Runnable { } - private void sendBoatLocationPackets() { - ArrayList yachts = new ArrayList<>(GameState.getYachts().values()); - for (Yacht yacht : yachts) { + public void sendBoatLocationPackets() { + ArrayList yachts = new ArrayList<>(GameState.getYachts().values()); + for (ServerYacht yacht : yachts) { BoatLocationMessage boatLocationMessage = new BoatLocationMessage( yacht.getSourceId(), @@ -308,7 +308,7 @@ public class ServerToClientThread implements Runnable { yacht.getLocation().getLat(), yacht.getLocation().getLng(), yacht.getHeading(), - yacht.getVelocity().longValue()); + yacht.getCurrentVelocity().longValue()); sendMessage(boatLocationMessage); } @@ -318,44 +318,18 @@ public class ServerToClientThread implements Runnable { return thread; } - public void sendRaceStatusMessage() { - // variables taken from GameServerThread - - List boatSubMessages = new ArrayList<>(); - BoatStatus boatStatus; - RaceStatus raceStatus; - - for (Player player : GameState.getPlayers()) { - Yacht y = player.getYacht(); - - if (GameState.getCurrentStage() == GameStages.PRE_RACE) { - boatStatus = BoatStatus.PRESTART; - } else if (GameState.getCurrentStage() == GameStages.RACING) { - boatStatus = BoatStatus.RACING; - } else { - boatStatus = BoatStatus.UNDEFINED; - } - - BoatSubMessage m = new BoatSubMessage(y.getSourceId(), boatStatus, 0, 0, 0, 1234l, - 1234l); - boatSubMessages.add(m); - } - - if (GameState.getCurrentStage() == GameStages.RACING) { - raceStatus = RaceStatus.STARTED; - } else { - raceStatus = RaceStatus.WARNING; - } - - sendMessage(new RaceStatusMessage(1, raceStatus, GameState.getStartTime(), GameState.getWindDirection(), - GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(), - RaceType.MATCH_RACE, 1, boatSubMessages)); - } - public Socket getSocket() { return socket; } + public ServerYacht getYacht() { + return yacht; + } + + public void sendCollisionMessage(Integer yachtId) { + sendMessage(new YachtEventCodeMessage(yachtId)); + } + public void addConnectionListener(ConnectionListener listener) { connectionListeners.add(listener); } diff --git a/src/main/java/seng302/gameServer/server/messages/MarkRoundingMessage.java b/src/main/java/seng302/gameServer/server/messages/MarkRoundingMessage.java index b683930e..f0d68e15 100644 --- a/src/main/java/seng302/gameServer/server/messages/MarkRoundingMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/MarkRoundingMessage.java @@ -1,5 +1,7 @@ package seng302.gameServer.server.messages; +import seng302.gameServer.GameState; + public class MarkRoundingMessage extends Message{ private final long MESSAGE_VERSION_NUMBER = 1; private final int MESSAGE_SIZE = 21; @@ -18,14 +20,14 @@ public class MarkRoundingMessage extends Message{ * The purpose of this is to record the time when yachts cross marks * @param ackNumber ackNumber * @param raceId raceId - * @param sourceId sourceId + * @param sourceId boatSourceId * @param roundingBoatStatus roundingBoatStatus * @param roundingSide roundingSide * @param markId markId */ public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus, - RoundingSide roundingSide, int markId){ - this.time = System.currentTimeMillis() / 1000L; + RoundingSide roundingSide, MarkType markType, int markId) { + this.time = System.currentTimeMillis(); this.ackNumber = ackNumber; this.raceId = raceId; this.sourceId = sourceId; @@ -44,6 +46,7 @@ public class MarkRoundingMessage extends Message{ putInt((int) sourceId, 4); putByte((byte) boatStatus.getCode()); putByte((byte) roundingSide.getCode()); + putByte((byte) markType.getCode()); putByte((byte) markId); writeCRC(); diff --git a/src/main/java/seng302/gameServer/server/messages/RoundingSide.java b/src/main/java/seng302/gameServer/server/messages/RoundingSide.java index 6bb4c553..efb4c929 100644 --- a/src/main/java/seng302/gameServer/server/messages/RoundingSide.java +++ b/src/main/java/seng302/gameServer/server/messages/RoundingSide.java @@ -4,17 +4,49 @@ package seng302.gameServer.server.messages; * The side the boat rounded the mark */ public enum RoundingSide { - UNKNOWN(0), - PORT(1), - STARBOARD(2); + UNKNOWN(0, "Unknown"), + PORT(1, "Port"), + STARBOARD(2, "Stbd"), + SP(3, "SP"), + PS(4, "PS"); + private long code; + private String name; - RoundingSide(long code) { + RoundingSide(long code, String name) { this.code = code; + this.name = name; } public long getCode(){ return code; } + + public String getName() { + return name; + } + + public static RoundingSide getRoundingSide(String identifier) { + RoundingSide roundingSide = UNKNOWN; + switch (identifier) { + case "Unknown": + roundingSide = UNKNOWN; + break; + case "Port": + roundingSide = PORT; + break; + case "Stbd": + roundingSide = STARBOARD; + break; + case "SP": + roundingSide = SP; + break; + case "PS": + roundingSide = PS; + break; + } + + return roundingSide; + } } diff --git a/src/main/java/seng302/gameServer/server/messages/YachtEventCodeMessage.java b/src/main/java/seng302/gameServer/server/messages/YachtEventCodeMessage.java new file mode 100644 index 00000000..08db575a --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/YachtEventCodeMessage.java @@ -0,0 +1,52 @@ +package seng302.gameServer.server.messages; + +/** + * Created by zyt10 on 10/08/17. + */ +public class YachtEventCodeMessage extends Message { + + private final MessageType MESSAGE_TYPE = MessageType.YACHT_EVENT_CODE; + private final int MESSAGE_VERSION = 1; //Always set to 1 + private final int MESSAGE_SIZE = 22; + + // Message fields + private long timeStamp; + private long ack = 0x00; //Unused + private int raceId; + private int destSourceId; + private int incidentId; + private int eventId; + + + public YachtEventCodeMessage(Integer subjectId) { + timeStamp = System.currentTimeMillis() / 1000L; + ack = 0; + raceId = 1; + destSourceId = subjectId; // collision boat source id + incidentId = 0; + eventId = 33; + + setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize())); + allocateBuffer(); + writeHeaderToBuffer(); + + // Write message fields + putUnsignedByte((byte) MESSAGE_VERSION); + putInt((int) timeStamp, 6); + putInt((int) ack, 2); + putInt((int) raceId, 4); + putInt((int) destSourceId, 4); + putInt((int) incidentId, 4); + putInt((int) eventId, 1); + + writeCRC(); + rewind(); + } + + /** + * @return The length of this message + */ + public int getSize() { + return MESSAGE_SIZE; + } +} diff --git a/src/main/java/seng302/gameServer/server/simulator/Boat.java b/src/main/java/seng302/gameServer/server/simulator/Boat.java deleted file mode 100644 index c083dbf2..00000000 --- a/src/main/java/seng302/gameServer/server/simulator/Boat.java +++ /dev/null @@ -1,125 +0,0 @@ -package seng302.gameServer.server.simulator; - -import seng302.model.GeoPoint; -import seng302.utilities.GeoUtility; - -public class Boat { - - private int sourceID; - private double lat; - private double lng; - private double speed; // in mm/sec - private String boatName, shortName, shorterName; - private boolean isFinished; - private long estimatedTimeTillFinish; - - private Corner lastPassedCorner, headingCorner; - - public Boat(int sourceID, String boatName) { - this.sourceID = sourceID; - this.boatName = boatName; - this.isFinished = false; - estimatedTimeTillFinish = 0; - } - - /** - * Moves boat to the heading direction for a given time duration - * @param heading moving direction in degree. - * @param duration moving duration in millisecond. - */ - public void move(double heading, double duration) { - Double distance = speed * duration / 1000000; // convert mm to meter - GeoPoint originPos = new GeoPoint(lat, lng); - GeoPoint newPos = GeoUtility.getGeoCoordinate(originPos, heading, distance); - this.lat = newPos.getLat(); - this.lng = newPos.getLng(); - } - - public String toString() { - return String.format("Boat (%d): lat: %f, lng: %f", sourceID, lat, lng); - } - - public int getSourceID() { - return sourceID; - } - - public void setSourceID(int sourceID) { - this.sourceID = sourceID; - } - - public double getLat() { - return lat; - } - - public void setLat(double lat) { - this.lat = lat; - } - - public double getLng() { - return lng; - } - - public void setLng(double lng) { - this.lng = lng; - } - - public double getSpeed() { - return speed; - } - - public void setSpeed(double speed) { - this.speed = speed; - } - - public String getBoatName() { - return boatName; - } - - public void setBoatName(String boatName) { - this.boatName = boatName; - } - - public String getShortName() { - return shortName; - } - - public void setShortName(String shortName) { - this.shortName = shortName; - } - - public String getShorterName() { - return shorterName; - } - - public void setShorterName(String shorterName) { - this.shorterName = shorterName; - } - - public Corner getLastPassedCorner() { - return lastPassedCorner; - } - - public void setLastPassedCorner(Corner lastPassedCorner) { - this.lastPassedCorner = lastPassedCorner; - } - - public Corner getHeadingCorner() { - return headingCorner; - } - - public void setHeadingCorner(Corner headingCorner) { - this.headingCorner = headingCorner; - } - - public boolean isFinished() { - return isFinished; - } - - public void setFinished(boolean finished) { - isFinished = finished; - } - - public long getEstimatedTimeTillFinish(){ - return (long) (-getSpeed()) + System.currentTimeMillis(); - } -} diff --git a/src/main/java/seng302/gameServer/server/simulator/Corner.java b/src/main/java/seng302/gameServer/server/simulator/Corner.java deleted file mode 100644 index 11c26104..00000000 --- a/src/main/java/seng302/gameServer/server/simulator/Corner.java +++ /dev/null @@ -1,91 +0,0 @@ -package seng302.gameServer.server.simulator; - -import seng302.model.mark.CompoundMark; - -public class Corner { - - private int seqID; - private CompoundMark compoundMark; - //private int CompoundMarkID; - private RoundingType roundingType; - private int zoneSize; // size of the zone around a mark in boat-lengths. - - // TODO: this shouldn't be used in the future!!!! - private double bearingToNextCorner, distanceToNextCorner; - private Corner nextCorner; - - public Corner(int seqID, CompoundMark compoundMark, RoundingType roundingType, int zoneSize) { - this.seqID = seqID; - this.compoundMark = compoundMark; - this.roundingType = roundingType; - this.zoneSize = zoneSize; - } - - /** - * Prints out corner's info and its compound mark, good for testing - * @return a string showing its details - */ - @Override - public String toString() { - return String.format("Corner: %d - %s - %d, %s\n", - seqID, roundingType.getType(), zoneSize, compoundMark.toString()); - } - - public int getSeqID() { - return seqID; - } - - public void setSeqID(int seqID) { - this.seqID = seqID; - } - - public CompoundMark getCompoundMark() { - return compoundMark; - } - - public void setCompoundMark(CompoundMark compoundMark) { - this.compoundMark = compoundMark; - } - - public RoundingType getRoundingType() { - return roundingType; - } - - public void setRoundingType(RoundingType roundingType) { - this.roundingType = roundingType; - } - - public int getZoneSize() { - return zoneSize; - } - - public void setZoneSize(int zoneSize) { - this.zoneSize = zoneSize; - } - - - // TODO: next six setters & getters shouldn't be used in the future. - public double getBearingToNextCorner() { - return bearingToNextCorner; - } - - public void setBearingToNextCorner(double bearingToNextCorner) { - this.bearingToNextCorner = bearingToNextCorner; - } - - public double getDistanceToNextCorner() { - return distanceToNextCorner; - } - - public void setDistanceToNextCorner(double distanceToNextCorner) { - this.distanceToNextCorner = distanceToNextCorner; - } - - public Corner getNextCorner() { - return nextCorner; - } - - public void setNextCorner(Corner nextCorner) { - this.nextCorner = nextCorner; - } -} diff --git a/src/main/java/seng302/gameServer/server/simulator/RoundingType.java b/src/main/java/seng302/gameServer/server/simulator/RoundingType.java deleted file mode 100644 index aa1bca8e..00000000 --- a/src/main/java/seng302/gameServer/server/simulator/RoundingType.java +++ /dev/null @@ -1,43 +0,0 @@ -package seng302.gameServer.server.simulator; - -public enum RoundingType { - - // the mark should be rounded to port (boat's left) - PORT("Port"), - - // the mark should be rounded to starboard (boat's right) - STARBOARD("Stbd"), - - // the boat within the compound mark with the SeqID of 1 should be rounded - // to starboard and the boat within the compound mark with the SeqID of 2 - // should be rounded to port. - SP("SP"), - - // the opposite of SP - PS("PS"); - - private String type; - - RoundingType(String type) { - this.type = type; - } - - public String getType() { - return this.type; - } - - public static RoundingType typeOf(String type) { - switch (type) { - case "Port": - return PORT; - case "Stbd": - return STARBOARD; - case "SP": - return SP; - case "PS": - return PS; - default: - return null; - } - } -} diff --git a/src/main/java/seng302/gameServer/server/simulator/parsers/BoatsParser.java b/src/main/java/seng302/gameServer/server/simulator/parsers/BoatsParser.java deleted file mode 100644 index 17a46eae..00000000 --- a/src/main/java/seng302/gameServer/server/simulator/parsers/BoatsParser.java +++ /dev/null @@ -1,19 +0,0 @@ -package seng302.gameServer.server.simulator.parsers; - -import org.w3c.dom.Document; - - -/** - * Parses the race xml file to get course details - * Created by Haoming Yin (hyi25) on 16/3/2017 - */ -public class BoatsParser extends FileParser { - - private Document doc; - - public BoatsParser(String path) { - super(path); - this.doc = this.parseFile(); - } - -} diff --git a/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java b/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java deleted file mode 100644 index 36164af2..00000000 --- a/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java +++ /dev/null @@ -1,118 +0,0 @@ -package seng302.gameServer.server.simulator.parsers; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import seng302.gameServer.server.simulator.Corner; -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 - * Created by Haoming Yin (hyi25) on 16/3/2017 - */ -public class CourseParser extends FileParser { - - private Document doc; - private Map compoundMarksMap; - - public CourseParser(String path) { - super(path); - this.doc = this.parseFile(); - } - - // TODO: should handle error / invalid file gracefully - protected List getCourse() { - compoundMarksMap = getCompoundMarks(doc.getDocumentElement()); - List corners = new ArrayList<>(); - NodeList cMarksSequence = doc.getElementsByTagName("Corner"); - - for (int i = 0; i < cMarksSequence.getLength(); i++) { - corners.add(getCorner(cMarksSequence.item(i))); - } - return corners; - } - - - private Corner getCorner(Node node) { - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element e = (Element) node; - - Integer seqId = Integer.valueOf(e.getAttribute("SeqID")); - Integer cMarkId = Integer.valueOf(e.getAttribute("CompoundMarkID")); - CompoundMark cMark = compoundMarksMap.get(cMarkId); - RoundingType roundingType = RoundingType.typeOf(e.getAttribute("Rounding")); - Integer zoneSize = Integer.valueOf(e.getAttribute("ZoneSize")); - - return new Corner(seqId, cMark, roundingType, zoneSize); - } - return null; - } - - private Map getCompoundMarks(Node node) { - Map compoundMarksMap = new HashMap<>(); - - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - NodeList cMarks = element.getElementsByTagName("CompoundMark"); - - // loop through all compound marks who are the children of course node - for (int i = 0; i < cMarks.getLength(); i++) { - CompoundMark cMark = getCompoundMark(cMarks.item(i)); - if (cMark != null) - compoundMarksMap.put(cMark.getId(), cMark); - } - - return compoundMarksMap; - } - return null; - } - - - private CompoundMark getCompoundMark(Node node) { - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element e = (Element) node; - Integer markID = Integer.valueOf(e.getAttribute("CompoundMarkID")); - String name = e.getAttribute("Name"); - - NodeList marks = e.getElementsByTagName("Mark"); - 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) { - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element e = (Element) node; - Integer seqId = Integer.valueOf(e.getAttribute("SeqID")); - String name = e.getAttribute("Name"); - Double lat = Double.valueOf(e.getAttribute("TargetLat")); - Double lng = Double.valueOf(e.getAttribute("TargetLng")); - Integer sourceId = Integer.valueOf(e.getAttribute("SourceID")); - - Mark mark = new Mark(name, lat, lng, sourceId); - mark.setSeqID(seqId); - - return mark; - } - System.out.println("Failed to create mark."); - return null; - } - -} diff --git a/src/main/java/seng302/gameServer/server/simulator/parsers/FileParser.java b/src/main/java/seng302/gameServer/server/simulator/parsers/FileParser.java deleted file mode 100644 index 87021893..00000000 --- a/src/main/java/seng302/gameServer/server/simulator/parsers/FileParser.java +++ /dev/null @@ -1,51 +0,0 @@ -package seng302.gameServer.server.simulator.parsers; - -import java.io.InputStream; -import java.io.StringReader; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import org.w3c.dom.Document; -import org.xml.sax.InputSource; - -/** - * Created by Haoming Yin (hyi25) on 16/3/2017 - */ -public abstract class FileParser { - - private String filePath; - - public FileParser() {} - - public FileParser(String path) { - this.filePath = path; - } - - protected Document parseFile() { - try { - InputStream is = getClass().getResourceAsStream(this.filePath); - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document doc = builder.parse(is); - // optional, in order to recover info from broken line. - doc.getDocumentElement().normalize(); - return doc; - } catch (Exception e) { - System.out.println("[FileParser] Exception"); - return null; - } - } - - protected Document parseFile(String xmlString) { - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document doc = builder.parse(new InputSource(new StringReader(xmlString))); - // optional, in order to recover info from broken line. - doc.getDocumentElement().normalize(); - return doc; - } catch (Exception e) { - System.out.println("[FileParser] Exception"); - } - return null; - } -} diff --git a/src/main/java/seng302/gameServer/server/simulator/parsers/RaceParser.java b/src/main/java/seng302/gameServer/server/simulator/parsers/RaceParser.java deleted file mode 100644 index 960ff8fc..00000000 --- a/src/main/java/seng302/gameServer/server/simulator/parsers/RaceParser.java +++ /dev/null @@ -1,65 +0,0 @@ -package seng302.gameServer.server.simulator.parsers; - -import java.util.ArrayList; -import java.util.List; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import seng302.gameServer.server.simulator.Boat; -import seng302.gameServer.server.simulator.Corner; - -/** - * Parses the race xml file to get course details - * Created by Haoming Yin (hyi25) on 16/3/2017 - */ -public class RaceParser extends FileParser { - - private Document doc; - private String path; - - public RaceParser(String path) { - super(path); - this.path = path; - this.doc = this.parseFile(); - } - - /** - * Parses race.xml file and returns a list of corner which is the race course. - * @return a list of ordered corner to represent the course. - */ - public List getCourse() { - CourseParser cp = new CourseParser(path); - return cp.getCourse(); - } - - /** - * Parses race.xml file and return a list of boats which will compete in the - * race. - * @return a list of boats that are going to compete in the race. - */ - public List getBoats() { - NodeList yachts = doc.getDocumentElement().getElementsByTagName("Yacht"); - List boats = new ArrayList<>(); - - for (int i = 0; i < yachts.getLength(); i++) { - boats.add(getBoat(yachts.item(i))); - } - return boats; - } - - /** - * Parses a single boat from the given node - * @param node a node within a boat tag - * @return a boat instance parsed from the given node - */ - private Boat getBoat(Node node) { - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element e = (Element) node; - - Integer sourceId = Integer.valueOf(e.getAttribute("SourceID")); - return new Boat(sourceId, "Test Boat"); - } - return null; - } -} diff --git a/src/main/java/seng302/model/ClientYacht.java b/src/main/java/seng302/model/ClientYacht.java new file mode 100644 index 00000000..564754b0 --- /dev/null +++ b/src/main/java/seng302/model/ClientYacht.java @@ -0,0 +1,268 @@ +package seng302.model; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import javafx.beans.property.ReadOnlyDoubleProperty; +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.model.mark.CompoundMark; + +/** + * Yacht class for the racing boat.

Class created to store more variables (eg. boat statuses) + * compared to the XMLParser boat class, also done outside Boat class because some old variables are + * not used anymore. + */ +public class ClientYacht extends Observable { + + @FunctionalInterface + public interface YachtLocationListener { + void notifyLocation(ClientYacht clientYacht, double lat, double lon, double heading, + Boolean sailsIn, double velocity); + } + + private Logger logger = LoggerFactory.getLogger(ClientYacht.class); + + + //BOTH AFAIK + private String boatType; + private Integer sourceId; + private String hullID; //matches HullNum in the XML spec. + private String shortName; + private String boatName; + private String country; + + private Long estimateTimeAtFinish; + private Boolean sailIn = false; + private Integer currentMarkSeqID = 0; + private Long markRoundTime; + private Long timeTillNext; + private Double heading; + private Integer legNumber = 0; + private GeoPoint location; + private Integer boatStatus; + private Double currentVelocity; + + //CLIENT SIDE + private List locationListeners = new ArrayList<>(); + private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper(); + private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper(); + private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper(); + private CompoundMark lastMarkRounded; + private Integer positionInt = 0; + private Color colour; + + public ClientYacht(String boatType, Integer sourceId, String hullID, String shortName, + String boatName, String country) { + this.boatType = boatType; + this.sourceId = sourceId; + this.hullID = hullID; + this.shortName = shortName; + this.boatName = boatName; + this.country = country; + this.location = new GeoPoint(57.670341, 11.826856); + this.heading = 120.0; //In degrees + this.currentVelocity = 0d; + this.boatStatus = 1; + } + + /** + * Add ServerToClientThread as the observer, this observer pattern mainly server for the boat + * rounding package. + */ + @Override + public void addObserver(Observer o) { + super.addObserver(o); + } + + public String getBoatType() { + return boatType; + } + + public Integer getSourceId() { + //@TODO Remove and merge with Creating Game Loop + if (sourceId == null) { + return 0; + } + return sourceId; + } + + public String getHullID() { + if (hullID == null) { + return ""; + } + return hullID; + } + + public String getShortName() { + return shortName; + } + + public String getBoatName() { + return boatName; + } + + public String getCountry() { + if (country == null) { + return ""; + } + return country; + } + + public Integer getBoatStatus() { + return boatStatus; + } + + public void setBoatStatus(Integer boatStatus) { + this.boatStatus = boatStatus; + } + + public Integer getLegNumber() { + return legNumber; + } + + public void setLegNumber(Integer legNumber) { + this.legNumber = legNumber; + } + + public void setEstimateTimeTillNextMark(Long estimateTimeTillNextMark) { + timeTillNext = estimateTimeTillNextMark; + } + + public String getEstimateTimeAtFinish() { + DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); + return format.format(estimateTimeAtFinish); + } + + public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) { + this.estimateTimeAtFinish = estimateTimeAtFinish; + } + + public Integer getPositionInteger() { + return positionInt; + } + + public void setPositionInteger(Integer position) { + this.positionInt = position; + } + + public void updateVelocityProperty(double velocity) { + this.velocityProperty.set(velocity); + } + + public void setMarkRoundingTime(Long markRoundingTime) { + this.markRoundTime = markRoundingTime; + } + + public ReadOnlyDoubleProperty getVelocityProperty() { + return velocityProperty.getReadOnlyProperty(); + } + + public ReadOnlyLongProperty timeTillNextProperty() { + return timeTillNextProperty.getReadOnlyProperty(); + } + + public Long getTimeTillNext() { + return timeTillNext; + } + + public Long getMarkRoundTime() { + return markRoundTime; + } + + public CompoundMark getLastMarkRounded() { + return lastMarkRounded; + } + + public void setLastMarkRounded(CompoundMark lastMarkRounded) { + this.lastMarkRounded = lastMarkRounded; + } + + public GeoPoint getLocation() { + return location; + } + + public void toggleSail() { + sailIn = !sailIn; + } + //// TODO: 15/08/17 asd + + /** + * Sets the current location of the boat in lat and long whilst preserving the last location + * + * @param lat Latitude + * @param lng Longitude + */ + public void setLocation(Double lat, Double lng) { + location.setLat(lat); + location.setLng(lng); + } + + public Double getHeading() { + return heading; + } + + public void setHeading(Double heading) { + this.heading = heading; + } + + @Override + public String toString() { + return boatName; + } + + public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) { + this.timeSinceLastMarkProperty.set(timeSinceLastMark); + } + + public ReadOnlyLongProperty timeSinceLastMarkProperty() { + return timeSinceLastMarkProperty.getReadOnlyProperty(); + } + + public void setTimeTillNext(Long timeTillNext) { + this.timeTillNext = timeTillNext; + } + + + public Color getColour() { + return colour; + } + + public void setColour(Color colour) { + this.colour = colour; + } + +// public Double getCurrentVelocity() { +// return currentVelocity; +// } +// +// public void setCurrentVelocity(Double currentVelocity) { +// this.currentVelocity = currentVelocity; +// } + + + public void updateLocation(double lat, double lng, double heading, double velocity) { + setLocation(lat, lng); + this.heading = heading; +// this.currentVelocity = velocity; + updateVelocityProperty(velocity); + for (YachtLocationListener yll : locationListeners) { + yll.notifyLocation(this, lat, lng, heading, sailIn, velocity); + } + } + + public void addLocationListener(YachtLocationListener listener) { + locationListeners.add(listener); + } + + public boolean getSailIn () { + return sailIn; + } +} diff --git a/src/main/java/seng302/model/Colors.java b/src/main/java/seng302/model/Colors.java index 72ff3ba5..81829262 100644 --- a/src/main/java/seng302/model/Colors.java +++ b/src/main/java/seng302/model/Colors.java @@ -6,12 +6,12 @@ import javafx.scene.paint.Color; * Enum for generating colours. */ public enum Colors { - RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE; + RED, PERU, GOLD, GREEN, BLUE, PURPLE, DEEPPINK, GRAY; static Integer index = 0; public static Color getColor() { - if (index == 6) { + if (index == 8) { index = 0; } return Color.valueOf(values()[index++].toString()); diff --git a/src/main/java/seng302/model/GeoPoint.java b/src/main/java/seng302/model/GeoPoint.java index 29938946..91937663 100644 --- a/src/main/java/seng302/model/GeoPoint.java +++ b/src/main/java/seng302/model/GeoPoint.java @@ -28,4 +28,9 @@ public class GeoPoint { public void setLng(double lng) { this.lng = lng; } + + @Override + public String toString() { + return "lat: " + lat + " lng: " + lng; + } } diff --git a/src/main/java/seng302/model/Player.java b/src/main/java/seng302/model/Player.java index 175b7a45..1ed2b6dc 100644 --- a/src/main/java/seng302/model/Player.java +++ b/src/main/java/seng302/model/Player.java @@ -9,11 +9,11 @@ import java.net.Socket; public class Player { private Socket socket; - private Yacht yacht; + private ServerYacht yacht; private Integer lastMarkPassed; - public Player(Socket socket, Yacht yacht) { + public Player(Socket socket, ServerYacht yacht) { this.socket = socket; this.yacht = yacht; } @@ -30,7 +30,7 @@ public class Player { this.lastMarkPassed = lastMarkPassed; } - public Yacht getYacht() { + public ServerYacht getYacht() { return yacht; } diff --git a/src/main/java/seng302/model/ServerYacht.java b/src/main/java/seng302/model/ServerYacht.java new file mode 100644 index 00000000..2f10929c --- /dev/null +++ b/src/main/java/seng302/model/ServerYacht.java @@ -0,0 +1,385 @@ +package seng302.model; + +import java.util.HashMap; +import java.util.Observable; +import java.util.Observer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import seng302.gameServer.GameState; +import seng302.gameServer.server.messages.BoatStatus; +import seng302.model.mark.Mark; +import seng302.utilities.GeoUtility; + +/** + * Yacht class for the racing boat.

Class created to store more variables (eg. boat statuses) + * compared to the XMLParser boat class, also done outside Boat class because some old variables are + * not used anymore. + */ +public class ServerYacht extends Observable { + + private Logger logger = LoggerFactory.getLogger(ClientYacht.class); + + public static final Double TURN_STEP = 5.0; + + //Boat info + private String boatType; + private Integer sourceId; + private String hullID; //matches HullNum in the XML spec. + private String shortName; + private String boatName; + private String country; + private BoatStatus boatStatus; + + + //Location + private Double lastHeading; + private Boolean sailIn; + private Double heading; + private GeoPoint lastLocation; + private GeoPoint location; + private Double currentVelocity; + private Boolean isAuto; + private Double autoHeading; + + //Mark Rounding + private Integer currentMarkSeqID; + private Boolean hasEnteredRoundingZone; + private Mark closestCurrentMark; + private Boolean hasPassedLine; + private Boolean hasPassedThroughGate; + + + public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName, + String boatName, String country) { + this.boatType = boatType; + this.boatStatus = BoatStatus.PRESTART; + this.sourceId = sourceId; + this.hullID = hullID; + 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.currentVelocity = 0d; //in mms-1 + this.currentMarkSeqID = 0; + + this.hasEnteredRoundingZone = false; + this.hasPassedLine = false; + this.hasPassedThroughGate = false; + } + + + /** + * Changes the boats current currentVelocity by a set amount, positive or negative + * + * @param velocityChange The ammount to change the currentVelocity by, in mms-1 + */ + public void changeVelocity(Double velocityChange) { + currentVelocity += velocityChange; + } + + /** + * Updates the boat to a new GeoPoint whilst preserving the last location + * + * @param secondsElapsed The seconds elapsed since the last update of this yacht + */ + public void updateLocation(Double secondsElapsed) { + lastLocation = location; + location = GeoUtility.getGeoCoordinate(location, heading, currentVelocity * secondsElapsed); + } + + public void setLocation(GeoPoint geoPoint) { + location = geoPoint; + } + + /** + * Add ServerToClientThread as the observer, this observer pattern mainly server for the boat + * rounding package. + */ + @Override + public void addObserver(Observer o) { + super.addObserver(o); + } + + /** + * 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) { + 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() { + sailIn = !sailIn; + } + + public void turnUpwind() { + disableAutoPilot(); + Double normalizedHeading = normalizeHeading(); + 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() { + disableAutoPilot(); + Double normalizedHeading = normalizeHeading(); + 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); + } + } + + /** + * 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() { + if (isAuto) { + disableAutoPilot(); + } else { + Double normalizedHeading = normalizeHeading(); + Double optimalHeading; + HashMap optimalPolarMap; + + if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind + optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots()); + } else { + 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); + return normalizedHeading; + } + + public Integer getSourceId() { + //@TODO Remove and merge with Creating Game Loop + if (sourceId == null) { + return 0; + } + return sourceId; + } + + // TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE + public String getHullID() { + if (hullID == null) { + return ""; + } + return hullID; + } + + // TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE + public String getShortName() { + return shortName; + } + + public String getBoatName() { + return boatName; + } + + public String getCountry() { + if (country == null) { + return ""; + } + return country; + } + + + public GeoPoint getLocation() { + return location; + } + + + public Double getHeading() { + return heading; + } + + public void setHeading(Double heading) { + this.heading = heading; + } + + public Boolean getSailIn() { + return sailIn; + } + + @Override + public String toString() { + return boatName; + } + + public Double getCurrentVelocity() { + return currentVelocity; + } + + public void setCurrentVelocity(Double currentVelocity) { + this.currentVelocity = currentVelocity; + } + + public Integer getCurrentMarkSeqID() { + return currentMarkSeqID; + } + + public GeoPoint getLastLocation() { + return lastLocation; + } + + public Mark getClosestCurrentMark() { + return closestCurrentMark; + } + + public void setClosestCurrentMark(Mark closestCurrentMark) { + this.closestCurrentMark = closestCurrentMark; + } + + public void setHasEnteredRoundingZone(Boolean hasEnteredRoundingZone) { + this.hasEnteredRoundingZone = hasEnteredRoundingZone; + } + + public void setHasPassedLine(Boolean hasPassedLine) { + this.hasPassedLine = hasPassedLine; + } + + public void setHasPassedThroughGate(Boolean hasPassedThroughGate) { + this.hasPassedThroughGate = hasPassedThroughGate; + } + + public BoatStatus getBoatStatus() { + return boatStatus; + } + + public void setBoatStatus(BoatStatus boatStatus) { + this.boatStatus = boatStatus; + } + + public void incrementMarkSeqID() { + currentMarkSeqID++; + } + + public Boolean hasEnteredRoundingZone() { + return hasEnteredRoundingZone; + } + + public Boolean hasPassedThroughGate() { + return hasPassedThroughGate; + } + + public Boolean hasPassedLine() { + return hasPassedLine; + } + + +} diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java deleted file mode 100644 index ed058773..00000000 --- a/src/main/java/seng302/model/Yacht.java +++ /dev/null @@ -1,680 +0,0 @@ -package seng302.model; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import javafx.beans.property.ReadOnlyDoubleProperty; -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; -import seng302.utilities.GeoUtility; - -/** - * Yacht class for the racing boat. - * - * Class created to store more variables (eg. boat statuses) compared to the XMLParser boat class, - * also done outside Boat class because some old variables are not used anymore. - */ -public class Yacht { - - - @FunctionalInterface - public interface YachtLocationListener { - void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity, boolean sailIn); - } - - 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; - private Integer sourceId; - private String hullID; //matches HullNum in the XML spec. - private String shortName; - private String boatName; - private String country; - - private Long estimateTimeAtFinish; - private Integer currentMarkSeqID = 0; - private Long markRoundTime; - private Double distanceToCurrentMark; - private Long timeTillNext; - private Double heading; - private Integer legNumber = 0; - - //SERVER SIDE - 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 hasPassedLine; - private Boolean hasPassedThroughGate; - private Boolean finishedRace; - - //CLIENT SIDE - private List locationListeners = new ArrayList<>(); - private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper(); - private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper(); - private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper(); - private CompoundMark lastMarkRounded; - private Integer positionInt = 0; - private Color colour; - private Boolean clientSailsIn = true; - - public Yacht(String boatType, Integer sourceId, String hullID, String shortName, - String boatName, String country) { - this.boatType = boatType; - this.sourceId = sourceId; - this.hullID = hullID; - 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.hasPassedLine = false; - this.hasPassedThroughGate = false; - this.finishedRace = false; - } - - /** - * @param timeInterval since last update in milliseconds - */ - public void update(Long timeInterval) { - - Double secondsElapsed = timeInterval / 1000000.0; - Double windSpeedKnots = GameState.getWindSpeedKnots(); - Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading); - Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle); - Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000; - if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) { - - if (velocity < maxBoatSpeed) { - velocity += maxBoatSpeed / 15; // Acceleration - } - if (velocity > maxBoatSpeed) { - velocity = maxBoatSpeed; // Prevent the boats from exceeding top speed - } - - } else { // Deceleration - - if (velocity > 0d) { - if (maxBoatSpeed != 0d) { - velocity -= maxBoatSpeed / 600; - } else { - velocity -= velocity / 100; - } - if (velocity < 0) { - velocity = 0d; - } - } - } - - runAutoPilot(); - - //UPDATE BOAT LOCATION - lastLocation = location; - location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); - - //CHECK FOR MARK ROUNDING - if (!finishedRace) { - checkForLegProgression(); - } - - // TODO: 3/08/17 wmu16 - Implement line cross check here - } - - - /** - * 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 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); - Double distance2 = GeoUtility.getDistance(location, sub2); - return (distance1 < distance2) ? distance1 : distance2; - } else { - return GeoUtility.getDistance(location, nextMark.getSubMark(1)); - } - } - - - /** - * 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) { - 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() { - sailIn = !sailIn; - } - - public void turnUpwind() { - disableAutoPilot(); - Double normalizedHeading = normalizeHeading(); - 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() { - disableAutoPilot(); - Double normalizedHeading = normalizeHeading(); - 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); - } - } - - /** - * 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() { - if (isAuto) { - disableAutoPilot(); - } else { - Double normalizedHeading = normalizeHeading(); - Double optimalHeading; - HashMap optimalPolarMap; - - if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind - optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots()); - } else { - 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); - return normalizedHeading; - } - - public String getBoatType() { - return boatType; - } - - public Integer getSourceId() { - //@TODO Remove and merge with Creating Game Loop - if (sourceId == null) return 0; - return sourceId; - } - - public String getHullID() { - if (hullID == null) return ""; - return hullID; - } - - public String getShortName() { - return shortName; - } - - public String getBoatName() { - return boatName; - } - - public String getCountry() { - if (country == null) return ""; - return country; - } - - public Integer getBoatStatus() { - return boatStatus; - } - - public void setBoatStatus(Integer boatStatus) { - this.boatStatus = boatStatus; - } - - public Integer getLegNumber() { - return legNumber; - } - - public void setLegNumber(Integer legNumber) { -// if (colour != null && position != "-" && legNumber != this.legNumber) { -// RaceViewController.updateYachtPositionSparkline(this, legNumber); -// } - this.legNumber = legNumber; - } - - public void setEstimateTimeTillNextMark(Long estimateTimeTillNextMark) { - timeTillNext = estimateTimeTillNextMark; - } - - public String getEstimateTimeAtFinish() { - DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); - return format.format(estimateTimeAtFinish); - } - - public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) { - this.estimateTimeAtFinish = estimateTimeAtFinish; - } - - public Integer getPositionInteger() { - return positionInt; - } - - public void setPositionInteger(Integer position) { - this.positionInt = position; - } - - public void updateVelocityProperty(double velocity) { - this.velocityProperty.set(velocity); - } - - - public void setMarkRoundingTime(Long markRoundingTime) { - this.markRoundTime = markRoundingTime; - } - - public ReadOnlyDoubleProperty getVelocityProperty() { - return velocityProperty.getReadOnlyProperty(); - } - - public double getVelocityMMS() { - return velocity; - } - - public ReadOnlyLongProperty timeTillNextProperty() { - return timeTillNextProperty.getReadOnlyProperty(); - } - - public Double getVelocityKnots() { - return velocity / 1000 * 1.943844492; // TODO: 26/07/17 cir27 - remove magic number - } - - public Long getTimeTillNext() { - return timeTillNext; - } - - public Long getMarkRoundTime() { - return markRoundTime; - } - - public CompoundMark getLastMarkRounded() { - return lastMarkRounded; - } - - public void setLastMarkRounded(CompoundMark lastMarkRounded) { - this.lastMarkRounded = lastMarkRounded; - } - - public GeoPoint getLocation() { - return location; - } - - /** - * Sets the current location of the boat in lat and long whilst preserving the last location - * - * @param lat Latitude - * @param lng Longitude - */ - public void setLocation(Double lat, Double lng) { - lastLocation.setLat(location.getLat()); - lastLocation.setLng(location.getLng()); - location.setLat(lat); - location.setLng(lng); - } - - public Double getHeading() { - return heading; - } - - public void setHeading(Double heading) { - this.heading = heading; - } - - public Boolean getSailIn() { - return sailIn; - } - - @Override - public String toString() { - return boatName; - } - - public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) { - this.timeSinceLastMarkProperty.set(timeSinceLastMark); - } - - public ReadOnlyLongProperty timeSinceLastMarkProperty () { - return timeSinceLastMarkProperty.getReadOnlyProperty(); - } - - public void setTimeTillNext(Long timeTillNext) { - this.timeTillNext = timeTillNext; - } - - - public Color getColour() { - return colour; - } - - public void setColour(Color colour) { - this.colour = colour; - } - - public void toggleClientSail() { - clientSailsIn = !clientSailsIn; - } - - public Double getVelocity() { - return velocity; - } - - public void setVelocity(Double velocity) { - this.velocity = velocity; - } - - public Double getDistanceToCurrentMark() { - return distanceToCurrentMark; - } - - public void updateLocation(double lat, double lng, double heading, double velocity) { - setLocation(lat, lng); - this.heading = heading; - this.velocity = velocity; - updateVelocityProperty(velocity); - for (YachtLocationListener yll : locationListeners) { - yll.notifyLocation(this, lat, lng, heading, velocity, clientSailsIn); - } - } - - 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 fe5147de..70cd114f 100644 --- a/src/main/java/seng302/model/mark/CompoundMark.java +++ b/src/main/java/seng302/model/mark/CompoundMark.java @@ -2,6 +2,7 @@ package seng302.model.mark; import java.util.ArrayList; import java.util.List; +import seng302.gameServer.server.messages.RoundingSide; import seng302.model.GeoPoint; import seng302.utilities.GeoUtility; @@ -9,7 +10,6 @@ public class CompoundMark { private int compoundMarkId; private String name; - private List marks = new ArrayList<>(); private GeoPoint midPoint; @@ -55,6 +55,27 @@ public class CompoundMark { this.name = name; } + public void setRoundingSide(RoundingSide roundingSide) { + switch (roundingSide) { + case SP: + getSubMark(1).setRoundingSide(RoundingSide.STARBOARD); + getSubMark(2).setRoundingSide(RoundingSide.PORT); + break; + case PS: + getSubMark(1).setRoundingSide(RoundingSide.PORT); + getSubMark(2).setRoundingSide(RoundingSide.STARBOARD); + break; + case PORT: + getSubMark(1).setRoundingSide(RoundingSide.PORT); + break; + case STARBOARD: + getSubMark(1).setRoundingSide(RoundingSide.STARBOARD); + break; + + + } + } + /** * Returns the mark contained in the compound mark. Marks are numbered 1 to n; * @param singleMarkId the id of the desired mark contained in this compound mark. diff --git a/src/main/java/seng302/model/mark/Mark.java b/src/main/java/seng302/model/mark/Mark.java index 57d04974..de66166c 100644 --- a/src/main/java/seng302/model/mark/Mark.java +++ b/src/main/java/seng302/model/mark/Mark.java @@ -2,6 +2,7 @@ package seng302.model.mark; import java.util.ArrayList; import java.util.List; +import seng302.gameServer.server.messages.RoundingSide; import seng302.model.GeoPoint; /** @@ -19,11 +20,13 @@ public class Mark extends GeoPoint { private String name; private int sourceID; private List positionListeners = new ArrayList<>(); + private RoundingSide roundingSide; - public Mark(String name, double lat, double lng, int sourceID) { + public Mark(String name, int seqID, double lat, double lng, int sourceID) { super(lat, lng); this.name = name; this.sourceID = sourceID; + this.seqID = seqID; } /** @@ -39,10 +42,6 @@ public class Mark extends GeoPoint { return seqID; } - public void setSeqID(int seqID) { - this.seqID = seqID; - } - public String getName() { return name; } @@ -55,6 +54,14 @@ public class Mark extends GeoPoint { return sourceID; } + public RoundingSide getRoundingSide() { + return roundingSide; + } + + public void setRoundingSide(RoundingSide roundingSide) { + this.roundingSide = roundingSide; + } + public void setSourceID(int sourceID) { this.sourceID = sourceID; } diff --git a/src/main/java/seng302/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java index 1b744fc2..aa75b494 100644 --- a/src/main/java/seng302/model/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -2,10 +2,6 @@ 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; @@ -14,18 +10,20 @@ import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import seng302.gameServer.server.messages.RoundingSide; import seng302.model.stream.xml.generator.Race; import seng302.model.stream.xml.parser.RaceXMLData; import seng302.utilities.XMLGenerator; import seng302.utilities.XMLParser; +import java.util.*; /** * Class to hold the order of the marks in the race. */ public class MarkOrder { - private List raceMarkOrder; private Logger logger = LoggerFactory.getLogger(MarkOrder.class); + private Set allMarks; public MarkOrder(){ loadRaceProperties(); @@ -75,6 +73,10 @@ public class MarkOrder { return raceMarkOrder.get(currentSeqID + 1); } + public Set getAllMarks(){ + return Collections.unmodifiableSet(allMarks); + } + /** * Loads the race order from an XML string * @param xml An AC35 RaceXML @@ -85,6 +87,7 @@ public class MarkOrder { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db; Document doc; + allMarks = new HashSet<>(); try { db = dbf.newDocumentBuilder(); @@ -104,7 +107,9 @@ public class MarkOrder { for (Corner corner : corners){ CompoundMark compoundMark = marks.get(corner.getCompoundMarkID()); + compoundMark.setRoundingSide(RoundingSide.getRoundingSide(corner.getRounding())); course.add(compoundMark); + allMarks.addAll(compoundMark.getMarks()); } return course; diff --git a/src/main/java/seng302/model/stream/parser/YachtEventData.java b/src/main/java/seng302/model/stream/parser/YachtEventData.java new file mode 100644 index 00000000..635bd11f --- /dev/null +++ b/src/main/java/seng302/model/stream/parser/YachtEventData.java @@ -0,0 +1,34 @@ +package seng302.model.stream.parser; + +/** + * Stores parsed data from yacht event code packet + */ +public class YachtEventData { + private Long subjectId; + private Long incidentId; + private Integer eventId; + private Long timeStamp; + + public YachtEventData(Long subjectId, Long incidentId, Integer eventId, Long timeStamp) { + this.subjectId = subjectId; + this.incidentId = incidentId; + this.eventId = eventId; + this.timeStamp = timeStamp; + } + + public Long getSubjectId() { + return subjectId; + } + + public Long getIncidentId() { + return incidentId; + } + + public Integer getEventId() { + return eventId; + } + + public Long getTimeStamp() { + return timeStamp; + } +} diff --git a/src/main/java/seng302/model/stream/xml/generator/Race.java b/src/main/java/seng302/model/stream/xml/generator/Race.java index c9f8cded..cf42cb2c 100644 --- a/src/main/java/seng302/model/stream/xml/generator/Race.java +++ b/src/main/java/seng302/model/stream/xml/generator/Race.java @@ -4,13 +4,14 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import seng302.model.Yacht; +import seng302.model.ServerYacht; /** * A Race object that can be parsed into XML */ public class Race { - private List yachts; + + private List yachts; private LocalDateTime startTime; public Race(){ @@ -22,7 +23,7 @@ public class Race { * Add a boat to the race * @param yacht The boat to add */ - public void addBoat(Yacht yacht){ + public void addBoat(ServerYacht yacht) { yachts.add(yacht); } @@ -30,7 +31,7 @@ public class Race { * Get a list of boats in the race * @return A List of boats */ - public List getBoats(){ + public List getBoats() { return Collections.unmodifiableList(yachts); } diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index f6968601..1c8c2d21 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -6,6 +6,7 @@ import seng302.model.GeoPoint; public class GeoUtility { private static double EARTH_RADIUS = 6378.137; + private static Double MS_TO_KNOTS = 1.943844492; /** * Calculates the euclidean distance between two markers on the canvas using xy coordinates @@ -234,4 +235,20 @@ public class GeoUtility { return true; } + + /** + * @param boatSpeedInKnots Speed in knots + * @return The Boat speed in millimeters per second + */ + public static Double knotsToMMS(Double boatSpeedInKnots) { + return boatSpeedInKnots / MS_TO_KNOTS * 1000; + } + + /** + * @param boatSpeedInMMS Speed in millimeters per second + * @return The Boat speed in knots + */ + public static Double mmsToKnots(Double boatSpeedInMMS) { + return boatSpeedInMMS / 1000 * MS_TO_KNOTS; + } } diff --git a/src/main/java/seng302/utilities/StreamParser.java b/src/main/java/seng302/utilities/StreamParser.java index 0f4c48c0..304b105d 100644 --- a/src/main/java/seng302/utilities/StreamParser.java +++ b/src/main/java/seng302/utilities/StreamParser.java @@ -13,15 +13,12 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; import seng302.model.stream.packets.PacketType; import seng302.model.stream.packets.StreamPacket; -import seng302.model.stream.parser.MarkRoundingData; -import seng302.model.stream.parser.PositionUpdateData; +import seng302.model.stream.parser.*; import seng302.model.stream.parser.PositionUpdateData.DeviceType; -import seng302.model.stream.parser.RaceStartData; -import seng302.model.stream.parser.RaceStatusData; /** - * StreamParser is a utilities class for taking byte data, formatted according to the AC35 - * streaming protocol, and parsing it into basic data types or collections. + * StreamParser is a utilities class for taking byte data, formatted according to the AC35 streaming + * protocol, and parsing it into basic data types or collections. * * Created by kre39 on 23/04/17. */ @@ -34,8 +31,9 @@ public class StreamParser { * @return the packet sequence number if the packet is of type HEARTBEAT, null otherwise. */ public static Long extractHeartBeat(StreamPacket packet) { - if (packet.getType() != PacketType.HEARTBEAT) + if (packet.getType() != PacketType.HEARTBEAT) { return null; + } long heartbeat = bytesToLong(packet.getPayload()); System.out.println("heartbeat = " + heartbeat); return heartbeat; @@ -52,16 +50,17 @@ public class StreamParser { * containing the parsed packet data. */ public static RaceStatusData extractRaceStatus(StreamPacket packet) { - if (packet.getType() != PacketType.RACE_STATUS) + if (packet.getType() != PacketType.RACE_STATUS) { return null; + } byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; long currentTime = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11)); int raceStatus = payload[11]; - long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18)); - long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20)); - long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22)); + long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload, 12, 18)); + long windDir = bytesToLong(Arrays.copyOfRange(payload, 18, 20)); + long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload, 20, 22)); // DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); // currentTime = format.format((new Date(currentTime))) @@ -70,7 +69,6 @@ public class StreamParser { windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime ); - // long timeTillStart = // ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000; // @@ -110,7 +108,7 @@ public class StreamParser { // boat.setEstimateTimeAtFinish(estTimeAtFinish); data.addBoatData(boatID, estTimeAtNextMark, estTimeAtFinish, leg, boatStatus); } - return data; + return data; } // private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){ @@ -139,8 +137,9 @@ public class StreamParser { * DISPLAY_TEXT_MESSAGE. */ public static List extractDisplayMessage(StreamPacket packet) { - if (packet.getType() != PacketType.DISPLAY_TEXT_MESSAGE) + if (packet.getType() != PacketType.DISPLAY_TEXT_MESSAGE) { return null; + } List message = new ArrayList<>(); byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -166,10 +165,11 @@ public class StreamParser { * XML_MESSAGE. */ public static Document extractXmlMessage(StreamPacket packet) { - if ( packet.getType() != PacketType.RACE_XML && - packet.getType() != PacketType.REGATTA_XML && - packet.getType() != PacketType.BOAT_XML ) + if (packet.getType() != PacketType.RACE_XML && + packet.getType() != PacketType.REGATTA_XML && + packet.getType() != PacketType.BOAT_XML) { return null; + } byte[] payload = packet.getPayload(); int messageType = payload[9]; @@ -194,8 +194,8 @@ public class StreamParser { * Extracts the race start status from the packet and returns it as a long array. * * @param packet Packet parsed in to use the payload - * @return An array of form [raceID, raceStartTime, notificationType, timeStamp] or null if - * the packet type is not of RACE_START_STATUS. + * @return An array of form [raceID, raceStartTime, notificationType, timeStamp] or null if the + * packet type is not of RACE_START_STATUS. */ public static RaceStartData extractRaceStartStatus(StreamPacket packet) { if (packet.getType() != PacketType.RACE_START_STATUS) { @@ -212,23 +212,25 @@ public class StreamParser { /** * Parses the the byte array in a StreamPacket for yacht events to retrieve the necessary info - * and returns it a an array of longs. + * and returns it as YachtEventData. * * @param packet Packet parsed in to use the payload - * @return the event data in the form [boatID, incidentID, eventID, timeStamp]. Returns null if - * the packet is not of type YACHT_EVENT_CODE. + * @return the event data in the form of YachtEventData. Returns null if the packet is not of + * type YACHT_EVENT_CODE. */ - public static long[] extractYachtEventCode(StreamPacket packet) { - if (packet.getType() != PacketType.YACHT_EVENT_CODE) + public static YachtEventData extractYachtEventCode(StreamPacket packet) { + if (packet.getType() != PacketType.YACHT_EVENT_CODE) { return null; + } byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); + long ackNumber = bytesToLong(Arrays.copyOfRange(payload, 7, 9)); long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13)); long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17)); long incidentId = bytesToLong(Arrays.copyOfRange(payload, 17, 21)); int eventId = payload[21]; - return new long[] {subjectId, incidentId, eventId, timeStamp}; + return new YachtEventData(subjectId, incidentId, eventId, timeStamp); } /** @@ -239,15 +241,16 @@ public class StreamParser { * Returns null if the packet is not of type YACHT_ACTION_CODE. */ public static long[] extractYachtActionCode(StreamPacket packet) { - if (packet.getType() != PacketType.YACHT_ACTION_CODE) + if (packet.getType() != PacketType.YACHT_ACTION_CODE) { return null; + } byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); long subjectId = bytesToLong(Arrays.copyOfRange(payload, 9, 13)); long incidentId = bytesToLong(Arrays.copyOfRange(payload, 13, 17)); int eventId = payload[17]; - return new long[] {subjectId, incidentId, eventId, timeStamp}; + return new long[]{subjectId, incidentId, eventId, timeStamp}; } /** @@ -258,8 +261,9 @@ public class StreamParser { * CHATTER_TEXT. */ public static String extractChatterText(StreamPacket packet) { - if (packet.getType() != PacketType.CHATTER_TEXT) + if (packet.getType() != PacketType.CHATTER_TEXT) { return null; + } byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; int messageType = payload[1]; @@ -276,8 +280,9 @@ public class StreamParser { * is not of type BOAT_LOCATION. */ public static PositionUpdateData extractBoatLocation(StreamPacket packet) { - if (packet.getType() != PacketType.BOAT_LOCATION) + if (packet.getType() != PacketType.BOAT_LOCATION) { return null; + } byte[] payload = packet.getPayload(); int deviceType = (int) payload[15]; long timeValid = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); @@ -293,10 +298,11 @@ public class StreamParser { double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0; DeviceType type; - if (deviceType == 1) + if (deviceType == 1) { type = DeviceType.YACHT_TYPE; - else + } else { type = DeviceType.MARK_TYPE; + } return new PositionUpdateData((int) boatId, type, lat, lon, heading, groundSpeed); } @@ -309,8 +315,9 @@ public class StreamParser { * if packet is not of type MARK_ROUNDING. */ public static MarkRoundingData extractMarkRounding(StreamPacket packet) { - if (packet.getType() != PacketType.MARK_ROUNDING) + if (packet.getType() != PacketType.MARK_ROUNDING) { return null; + } byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); @@ -325,16 +332,17 @@ public class StreamParser { } /** - * Returns a list containing the string value of data within the given stream packet for - * course wind. + * Returns a list containing the string value of data within the given stream packet for course + * wind. * * @param packet The packet containing the payload * @return the string values of the wind packet. Returns null if the packet is not of type * COURSE_WIND. */ public static List extractCourseWind(StreamPacket packet) { - if (packet.getType() != PacketType.COURSE_WIND) + if (packet.getType() != PacketType.COURSE_WIND) { return null; + } byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; int selectedWindId = payload[1]; @@ -366,13 +374,13 @@ public class StreamParser { * Returns the parsed data from a StreamPacket for average wind data. * * @param packet The packet containing the payload - * @return The wind data in the form - * [rawPeriod, rawSamplePeriod, period2, speed2, period3, speed3, period4, speed4, timestamp] - * or null if the packet is not of type AVG_WIND. + * @return The wind data in the form [rawPeriod, rawSamplePeriod, period2, speed2, period3, + * speed3, period4, speed4, timestamp] or null if the packet is not of type AVG_WIND. */ public static long[] extractAvgWind(StreamPacket packet) { - if (packet.getType() != PacketType.AVG_WIND) + if (packet.getType() != PacketType.AVG_WIND) { return null; + } byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); @@ -384,7 +392,7 @@ public class StreamParser { long speed3 = bytesToLong(Arrays.copyOfRange(payload, 17, 19)); long period4 = bytesToLong(Arrays.copyOfRange(payload, 19, 21)); long speed4 = bytesToLong(Arrays.copyOfRange(payload, 21, 23)); - return new long[] { + return new long[]{ rawPeriod, rawSamplePeriod, period2, speed2, period3, speed3, period4, speed4, timeStamp }; } @@ -410,8 +418,7 @@ public class StreamParser { } /** - * takes an array of up to 7 bytes and returns a positive - * long constructed from the input bytes + * takes an array of up to 7 bytes and returns a positive long constructed from the input bytes * * @param bytes the byte array to conver to Long * @return a positive long if there is less than 7 bytes -1 otherwise diff --git a/src/main/java/seng302/utilities/XMLParser.java b/src/main/java/seng302/utilities/XMLParser.java index c4c4348b..a08a0769 100644 --- a/src/main/java/seng302/utilities/XMLParser.java +++ b/src/main/java/seng302/utilities/XMLParser.java @@ -8,8 +8,8 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import seng302.model.ClientYacht; import seng302.model.Limit; -import seng302.model.Yacht; import seng302.model.mark.CompoundMark; import seng302.model.mark.Corner; import seng302.model.mark.Mark; @@ -125,8 +125,8 @@ public class XMLParser { * @param doc XML Document Object * @return Mapping of sourceIds to Boats. */ - public static Map parseBoats(Document doc){ - Map competingBoats = new HashMap<>(); + public static Map parseBoats(Document doc) { + Map competingBoats = new HashMap<>(); Element docEle = doc.getDocumentElement(); @@ -135,7 +135,8 @@ public class XMLParser { Node currentBoat = boatsList.item(i); if (currentBoat.getNodeName().equals("Boat")) { // Boat boat = new Boat(currentBoat); - Yacht yacht = new Yacht(XMLParser.getNodeAttributeString(currentBoat, "Type"), + ClientYacht yacht = new ClientYacht( + XMLParser.getNodeAttributeString(currentBoat, "Type"), XMLParser.getNodeAttributeInt(currentBoat, "SourceID"), XMLParser.getNodeAttributeString(currentBoat, "HullNum"), XMLParser.getNodeAttributeString(currentBoat, "ShortName"), @@ -277,11 +278,12 @@ public class XMLParser { for (int i = 0; i < childMarks.getLength(); i++) { Node markNode = childMarks.item(i); if (markNode.getNodeName().equals("Mark")) { + Integer seqID = XMLParser.getNodeAttributeInt(markNode, "SeqID"); Integer sourceID = XMLParser.getNodeAttributeInt(markNode, "SourceID"); String markName = XMLParser.getNodeAttributeString(markNode, "Name"); Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "TargetLat"); Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "TargetLng"); - Mark mark = new Mark(markName, targetLat, targetLng, sourceID); + Mark mark = new Mark(markName, seqID, targetLat, targetLng, sourceID); subMarks.add(mark); } } diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index f4a68a5a..dd90e92c 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -14,13 +14,14 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.Pane; import seng302.gameServer.MainServerThread; import seng302.gameServer.server.messages.BoatAction; +import seng302.model.ClientYacht; import seng302.model.RaceState; -import seng302.model.Yacht; import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.parser.MarkRoundingData; import seng302.model.stream.parser.PositionUpdateData; import seng302.model.stream.parser.PositionUpdateData.DeviceType; import seng302.model.stream.parser.RaceStatusData; +import seng302.model.stream.parser.YachtEventData; import seng302.model.stream.xml.parser.RaceXMLData; import seng302.model.stream.xml.parser.RegattaXMLData; import seng302.utilities.StreamParser; @@ -41,7 +42,7 @@ public class GameClient { private RaceViewController raceView; - private Map allBoatsMap; + private Map allBoatsMap; private RegattaXMLData regattaData; private RaceXMLData courseData; private RaceState raceState = new RaceState(); @@ -102,6 +103,8 @@ public class GameClient { loadStartScreen(); } }); + + server.setGameClient(this); } private void loadStartScreen() { @@ -126,7 +129,8 @@ public class GameClient { * @return the lobby controller. */ private LobbyController loadLobby() { - FXMLLoader fxmlLoader = new FXMLLoader(GameClient.class.getResource("/views/LobbyView.fxml")); + FXMLLoader fxmlLoader = new FXMLLoader( + GameClient.class.getResource("/views/LobbyView.fxml")); try { holderPane.getChildren().clear(); holderPane.getChildren().add(fxmlLoader.load()); @@ -151,10 +155,24 @@ public class GameClient { holderPane.getScene().setOnKeyPressed(this::keyPressed); holderPane.getScene().setOnKeyReleased(this::keyReleased); raceView = fxmlLoader.getController(); - Yacht player = allBoatsMap.get(socketThread.getClientId()); + ClientYacht player = allBoatsMap.get(socketThread.getClientId()); raceView.loadRace(allBoatsMap, courseData, raceState, player); } + private void loadFinishScreenView() { + FXMLLoader fxmlLoader = new FXMLLoader( + getClass().getResource("/views/FinishScreenView.fxml")); + try { + final Node finishScreenFX = fxmlLoader.load(); + Platform.runLater(() -> { + holderPane.getChildren().clear(); + holderPane.getChildren().add(finishScreenFX); + }); + } catch (IOException e) { + e.printStackTrace(); + } + } + private void parsePackets() { while (socketThread.getPacketQueue().peek() != null) { StreamPacket packet = socketThread.getPacketQueue().poll(); @@ -205,13 +223,18 @@ public class GameClient { case MARK_ROUNDING: updateMarkRounding(StreamParser.extractMarkRounding(packet)); break; + + case YACHT_EVENT_CODE: + showCollisionAlert(StreamParser.extractYachtEventCode(packet)); + break; } } } private void startRaceIfAllDataReceived() { - if (allXMLReceived() && raceView == null) + if (allXMLReceived() && raceView == null) { loadRaceView(); + } } private boolean allXMLReceived() { @@ -224,8 +247,8 @@ public class GameClient { private void updatePosition(PositionUpdateData positionData) { if (positionData.getType() == DeviceType.YACHT_TYPE) { if (allXMLReceived() && allBoatsMap.containsKey(positionData.getDeviceId())) { - Yacht yacht = allBoatsMap.get(positionData.getDeviceId()); - yacht.updateLocation(positionData.getLat(), + ClientYacht clientYacht = allBoatsMap.get(positionData.getDeviceId()); + clientYacht.updateLocation(positionData.getLat(), positionData.getLon(), positionData.getHeading(), positionData.getGroundSpeed()); } @@ -242,11 +265,11 @@ public class GameClient { */ private void updateMarkRounding(MarkRoundingData roundingData) { if (allXMLReceived()) { - Yacht yacht = allBoatsMap.get(roundingData.getBoatId()); - yacht.setMarkRoundingTime(roundingData.getTimeStamp()); - yacht.updateTimeSinceLastMarkProperty( + ClientYacht clientYacht = allBoatsMap.get(roundingData.getBoatId()); + clientYacht.setMarkRoundingTime(roundingData.getTimeStamp()); + clientYacht.updateTimeSinceLastMarkProperty( raceState.getRaceTime() - roundingData.getTimeStamp()); - yacht.setLastMarkRounded( + clientYacht.setLastMarkRounded( courseData.getCompoundMarks().get( roundingData.getMarkId() ) @@ -257,23 +280,35 @@ public class GameClient { private void processRaceStatusUpdate(RaceStatusData data) { if (allXMLReceived()) { raceState.updateState(data); - if (raceView != null) - raceView.getGameView().setWindDir(raceState.getWindDirection()); + if (raceView != null) { + raceView.getGameView().setWindDir(raceState.getWindDirection()); + } + boolean raceFinished = true; + for (ClientYacht yacht : allBoatsMap.values()) { + if (yacht.getBoatStatus() != 3) { + raceFinished = false; + } + } + if (raceFinished == true) { + close(); + loadFinishScreenView(); + } + for (long[] boatData : data.getBoatData()) { - Yacht yacht = allBoatsMap.get((int) boatData[0]); - yacht.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]); - yacht.setEstimateTimeAtFinish(boatData[2]); + ClientYacht clientYacht = allBoatsMap.get((int) boatData[0]); + clientYacht.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]); + clientYacht.setEstimateTimeAtFinish(boatData[2]); int legNumber = (int) boatData[3]; - yacht.setLegNumber(legNumber); - yacht.setBoatStatus((int) boatData[4]); - if (legNumber != yacht.getLegNumber()) { + clientYacht.setLegNumber(legNumber); + clientYacht.setBoatStatus((int) boatData[4]); + if (legNumber != clientYacht.getLegNumber()) { int placing = 1; - for (Yacht otherYacht : allBoatsMap.values()) { - if (otherYacht.getSourceId() != boatData[0] && - yacht.getLegNumber() <= otherYacht.getLegNumber()) + for (ClientYacht otherClientYacht : allBoatsMap.values()) { + if (otherClientYacht.getSourceId() != boatData[0] && + clientYacht.getLegNumber() <= otherClientYacht.getLegNumber()) placing++; } - yacht.setPositionInteger(placing); + clientYacht.setPositionInteger(placing); } } } @@ -288,7 +323,7 @@ public class GameClient { * Handle the key-pressed event from the text field. * @param e The key event triggering this call */ - public void keyPressed(KeyEvent e) { + private void keyPressed(KeyEvent e) { switch (e.getCode()) { case SPACE: // align with vmg socketThread.sendBoatAction(BoatAction.VMG); break; @@ -298,12 +333,13 @@ public class GameClient { socketThread.sendBoatAction(BoatAction.DOWNWIND); break; case ENTER: // tack/gybe socketThread.sendBoatAction(BoatAction.TACK_GYBE); break; - case Z: // zoom in - raceView.getGameView().zoomIn(); - break; - case X: // zoom out - raceView.getGameView().zoomOut(); - break; + //TODO Allow a zoom in and zoom out methods + case Z: // zoom in + raceView.getGameView().zoomIn(); + break; + case X: // zoom out + raceView.getGameView().zoomOut(); + break; } } @@ -313,12 +349,25 @@ public class GameClient { //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet) case SHIFT: // sails in/sails out socketThread.sendBoatAction(BoatAction.SAILS_IN); - raceView.getGameView().getPlayerYacht().toggleClientSail(); + raceView.getGameView().getPlayerYacht().toggleSail(); break; case PAGE_UP: case PAGE_DOWN: socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break; + } + } + public RaceXMLData getCourseData() { + return courseData; + } + + /** + * Tells race view to show a collision animation. + */ + private void showCollisionAlert(YachtEventData yachtEventData) { + // 33 is the agreed code to show collision + if (yachtEventData.getEventId() == 33) { + raceView.showCollision(yachtEventData.getSubjectId()); } } } diff --git a/src/main/java/seng302/visualiser/GameView.java b/src/main/java/seng302/visualiser/GameView.java index 82c70f63..4f4a9fcc 100644 --- a/src/main/java/seng302/visualiser/GameView.java +++ b/src/main/java/seng302/visualiser/GameView.java @@ -7,6 +7,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import javafx.animation.AnimationTimer; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; import javafx.application.Platform; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.collections.ObservableList; @@ -18,13 +21,15 @@ import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; import seng302.gameServer.server.simulator.Boat; +import seng302.model.ClientYacht; +import javafx.util.Duration; import seng302.model.Colors; import seng302.model.GeoPoint; import seng302.model.Limit; -import seng302.model.Yacht; import seng302.model.mark.CompoundMark; import seng302.model.mark.Corner; import seng302.model.mark.Mark; @@ -42,10 +47,10 @@ import seng302.visualiser.map.CanvasMap; */ public class GameView extends Pane { - private double bufferSize = 50; - private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias. - private double panelHeight = 960; - private double canvasWidth = 1100; + private double bufferSize = 50; + private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias. + private double panelHeight = 960; + private double canvasWidth = 1100; private double canvasHeight = 920; private boolean horizontalInversion = false; @@ -65,8 +70,8 @@ public class GameView extends Pane { private List borderPoints; private Map markerObjects; - private Map boatObjects = new HashMap<>(); - private Map annotations = new HashMap<>(); + private Map boatObjects = new HashMap<>(); + private Map annotations = new HashMap<>(); private ObservableList gameObjects; private BoatObject selectedBoat = null; private Group annotationsGroup = new Group(); @@ -85,7 +90,7 @@ public class GameView extends Pane { private Double frameRate = 60.0; private int frameTimeIndex = 0; private boolean arrayFilled = false; - private Yacht playerYacht; + private ClientYacht playerYacht; private double windDir = 0.0; double scaleFactor = 1; @@ -107,6 +112,7 @@ public class GameView extends Pane { VERTICAL } + private void trackBoat() { if (selectedBoat != null) { double x = selectedBoat.getBoatLayoutX(); @@ -152,7 +158,7 @@ public class GameView extends Pane { initializeTimer(); } - private void initializeTimer () { + private void initializeTimer() { Arrays.fill(frameTimes, 1_000_000_000 / 60); timer = new AnimationTimer() { private long lastTime = 0; @@ -194,8 +200,8 @@ public class GameView extends Pane { } /** - * First find the top right and bottom left points' geo locations, then retrieve - * map from google to display on image view. - Haoming 22/5/2017 + * First find the top right and bottom left points' geo locations, then retrieve map from google + * to display on image view. - Haoming 22/5/2017 */ private void drawGoogleMap() { findMetersPerPixel(); @@ -253,13 +259,23 @@ public class GameView extends Pane { for (Mark mark : cMark.getMarks()) { makeAndBindMarker(mark, colour); } + + //UNCOMMENT THIS TO HIGHLIGHT SUBMARKS 1 and 2 RED AND GREEN RESPECTIVELY FOR DEBUG + //(instead of above for loop) +// for (Mark mark : cMark.getMarks()) { +// if (mark.getSeqID() == 1) { +// makeAndBindMarker(mark, Color.RED); +// } else { +// makeAndBindMarker(mark, Color.GREEN); +// } +// } //Create gate line if (cMark.isGate()) { for (int i = 1; i < cMark.getMarks().size(); i++) { gates.add( makeAndBindGate( markerObjects.get(cMark.getSubMark(i)), - markerObjects.get(cMark.getSubMark(i+1)), + markerObjects.get(cMark.getSubMark(i + 1)), colour ) ); @@ -322,7 +338,7 @@ public class GameView extends Pane { gate.endYProperty().bind( m2.centerYProperty() ); - return gate; + return gate; } /** @@ -376,24 +392,24 @@ public class GameView extends Pane { /** * Draws all the boats. - * @param yachts The yachts to set in the race + * @param clientYachts The yachts to set in the race */ - public void setBoats(List yachts) { + public void setBoats(List clientYachts) { BoatObject newBoat; final List wakes = new ArrayList<>(); - for (Yacht yacht : yachts) { + for (ClientYacht clientYacht : clientYachts) { Paint colour = Colors.getColor(); newBoat = new BoatObject(); newBoat.addSelectedBoatListener(this::setSelectedBoat); newBoat.setFill(colour); - boatObjects.put(yacht, newBoat); - createAndBindAnnotationBox(yacht, colour); + boatObjects.put(clientYacht, newBoat); + createAndBindAnnotationBox(clientYacht, colour); // wakesGroup.getChildren().add(newBoat.getWake()); wakes.add(newBoat.getWake()); boatObjectGroup.getChildren().add(newBoat); trails.getChildren().add(newBoat.getTrail()); // TODO: 1/08/17 Make this less vile to look at. - yacht.addLocationListener((boat, lat, lon, heading, velocity, sailIn) ->{ + clientYacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> { BoatObject bo = boatObjects.get(boat); Point2D p2d = findScaledXY(lat, lon); bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir); @@ -412,14 +428,13 @@ public class GameView extends Pane { gameObjects.addAll(annotationsGroup); gameObjects.addAll(boatObjectGroup); }); - } - private void createAndBindAnnotationBox (Yacht yacht, Paint colour) { + private void createAndBindAnnotationBox(ClientYacht clientYacht, Paint colour) { AnnotationBox newAnnotation = new AnnotationBox(); newAnnotation.setFill(colour); newAnnotation.addAnnotation( - "name", "Player: " + yacht.getShortName() + "name", "Player: " + clientYacht.getShortName() ); // newAnnotation.addAnnotation( // "velocity", @@ -442,28 +457,28 @@ public class GameView extends Pane { // return format.format(time); // } // ); - annotations.put(yacht, newAnnotation); + annotations.put(clientYacht, newAnnotation); } - private void drawFps(Double fps){ + private void drawFps(Double fps) { Platform.runLater(() -> fpsDisplay.setText(String.format("%d FPS", Math.round(fps)))); } /** - * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point - * with the leftmost point, rightmost point, southern most point and northern most point + * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with + * the leftmost point, rightmost point, southern most point and northern most point * respectively. */ private void findMinMaxPoint(List points) { List sortedPoints = new ArrayList<>(points); sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat)); minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng()); - GeoPoint maxLat = sortedPoints.get(sortedPoints.size()-1); + GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1); maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng()); sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng)); minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng()); - GeoPoint maxLon = sortedPoints.get(sortedPoints.size()-1); + GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1); maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng()); if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) { horizontalInversion = true; @@ -483,15 +498,19 @@ public class GameView extends Pane { if (scaleDirection == ScaleDirection.HORIZONTAL) { referenceAngle = Math.abs( - GeoUtility.getBearingRad(referencePoint, minLonPoint) + GeoUtility.getBearingRad(referencePoint, minLonPoint) ); - referencePointX = bufferSize + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility.getDistance(referencePoint, minLonPoint); + referencePointX = + bufferSize + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility + .getDistance(referencePoint, minLonPoint); referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint)); - referencePointY = canvasHeight - (bufferSize + bufferSize); - referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility.getDistance(referencePoint, maxLatPoint); - referencePointY = referencePointY / 2; + referencePointY = canvasHeight - (bufferSize + bufferSize); + referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility + .getDistance(referencePoint, maxLatPoint); + referencePointY = referencePointY / 2; referencePointY += bufferSize; - referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility.getDistance(referencePoint, maxLatPoint); + referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility + .getDistance(referencePoint, maxLatPoint); } else { referencePointY = canvasHeight - bufferSize; referenceAngle = Math.abs( @@ -499,11 +518,14 @@ public class GameView extends Pane { GeoUtility.getDistance(referencePoint, minLonPoint) ) ); - referencePointX = bufferSize; - referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility.getDistance(referencePoint, minLonPoint); - referencePointX += ((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor)) / 2; + referencePointX = bufferSize; + referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility + .getDistance(referencePoint, minLonPoint); + referencePointX += + ((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor)) + / 2; } - if(horizontalInversion) { + if (horizontalInversion) { referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize); } } @@ -516,12 +538,12 @@ public class GameView extends Pane { private double scaleRaceExtremities() { double vertAngle = Math.abs( - GeoUtility.getBearingRad(minLatPoint, maxLatPoint) + GeoUtility.getBearingRad(minLatPoint, maxLatPoint) ); double vertDistance = Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint); double horiAngle = Math.abs( - GeoUtility.getBearingRad(minLonPoint, maxLonPoint) + GeoUtility.getBearingRad(minLonPoint, maxLonPoint) ); if (horiAngle <= (Math.PI / 2)) { horiAngle = (Math.PI / 2) - horiAngle; @@ -547,35 +569,43 @@ public class GameView extends Pane { return findScaledXY(unscaled.getLat(), unscaled.getLng()); } - private Point2D findScaledXY (double unscaledLat, double unscaledLon) { + private Point2D findScaledXY(double unscaledLat, double unscaledLon) { double distanceFromReference; double angleFromReference; double xAxisLocation = referencePointX; double yAxisLocation = referencePointY; angleFromReference = GeoUtility.getBearingRad( - minLatPoint, new GeoPoint(unscaledLat, unscaledLon) + minLatPoint, new GeoPoint(unscaledLat, unscaledLon) ); distanceFromReference = GeoUtility.getDistance( minLatPoint, new GeoPoint(unscaledLat, unscaledLon) ); if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) { - xAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - yAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + xAxisLocation += Math + .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + yAxisLocation -= Math + .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); } else if (angleFromReference >= 0) { angleFromReference = angleFromReference - Math.PI / 2; - xAxisLocation += Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - yAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + xAxisLocation += Math + .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + yAxisLocation += Math + .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); } else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) { angleFromReference = Math.abs(angleFromReference); - xAxisLocation -= Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - yAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + xAxisLocation -= Math + .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + yAxisLocation -= Math + .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); } else { angleFromReference = Math.abs(angleFromReference) - Math.PI / 2; - xAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - yAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + xAxisLocation -= Math + .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + yAxisLocation += Math + .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); } - if(horizontalInversion) { + if (horizontalInversion) { xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize); } return new Point2D(xAxisLocation, yAxisLocation); @@ -602,7 +632,7 @@ public class GameView extends Pane { metersPerPixelY = dVertical / dy; } - public void setAnnotationVisibilities (boolean teamName, boolean velocity, boolean estTime, + public void setAnnotationVisibilities(boolean teamName, boolean velocity, boolean estTime, boolean legTime, boolean trail, boolean wake) { for (BoatObject boatObject : boatObjects.values()) { boatObject.setVisibility(teamName, velocity, estTime, legTime, trail, wake); @@ -615,17 +645,17 @@ public class GameView extends Pane { } } - public void setFPSVisibility (boolean visibility) { + public void setFPSVisibility(boolean visibility) { fpsDisplay.setVisible(visibility); } - public void selectBoat (Yacht selectedYacht) { + public void selectBoat(ClientYacht selectedClientYacht) { boatObjects.forEach((boat, group) -> - group.setIsSelected(boat == selectedYacht) + group.setIsSelected(boat == selectedClientYacht) ); } - public void pauseRace () { + public void pauseRace() { timer.stop(); } @@ -634,18 +664,17 @@ public class GameView extends Pane { this.windDir = windDir; } - - public void startRace () { + public void startRace() { timer.start(); } - public Yacht getPlayerYacht() { + public ClientYacht getPlayerYacht() { return playerYacht; } - public void setBoatAsPlayer (Yacht playerYacht) { + + public void setBoatAsPlayer (ClientYacht playerYacht) { this.playerYacht = playerYacht; - this.playerYacht.toggleClientSail(); boatObjects.get(playerYacht).setAsPlayer(); annotations.get(playerYacht).addAnnotation( "velocity", @@ -658,7 +687,42 @@ public class GameView extends Pane { annotationsGroup.getChildren().remove(annotations.get(playerYacht)); gameObjects.add(annotations.get(playerYacht)); }); + } + /** + * Given yacht geopoint by race view controller, drawCollision will calculate canvas X and Y and + * display a flashing red circle on collision point. + * + * @param collisionPoint yacht collision point + */ + public void drawCollision(GeoPoint collisionPoint) { + Platform.runLater(() -> { + Point2D point = findScaledXY(collisionPoint); + double circleRadius = 0.0; + Circle circle = new Circle(point.getX(), point.getY(), circleRadius, Color.RED); + gameObjects.add(circle); + + circle.setFill(Color.TRANSPARENT); + circle.setStroke(Color.RED); + circle.setStrokeWidth(3); + + Timeline timeline = new Timeline(); + timeline.setCycleCount(1); + + KeyFrame keyframe1 = new KeyFrame(Duration.ZERO, + new KeyValue(circle.radiusProperty(), 0), + new KeyValue(circle.strokeProperty(), Color.TRANSPARENT)); + KeyFrame keyFrame2 = new KeyFrame(new Duration(1000), + new KeyValue(circle.radiusProperty(), 50), + new KeyValue(circle.strokeProperty(), Color.RED)); + KeyFrame keyFrame3 = new KeyFrame(new Duration(1500), + new KeyValue(circle.strokeProperty(), Color.TRANSPARENT)); + + timeline.getKeyFrames().addAll(keyframe1, keyFrame2, keyFrame3); + timeline.play(); + + timeline.setOnFinished(event -> gameObjects.remove(circle)); + }); } public void setFrameRateFXText(Text fpsDisplay) { diff --git a/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java b/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java index db2a5d17..a4dc831b 100644 --- a/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java +++ b/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java @@ -17,24 +17,24 @@ import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; -import seng302.model.Yacht; +import seng302.model.ClientYacht; public class FinishScreenViewController implements Initializable { @FXML private GridPane finishScreenGridPane; @FXML - private TableView finishOrderTable; + private TableView finishOrderTable; @FXML - private TableColumn posCol; + private TableColumn posCol; @FXML - private TableColumn boatNameCol; + private TableColumn boatNameCol; @FXML - private TableColumn shortNameCol; + private TableColumn shortNameCol; @FXML - private TableColumn countryCol; + private TableColumn countryCol; - ObservableList data = FXCollections.observableArrayList(); + ObservableList data = FXCollections.observableArrayList(); @Override public void initialize(URL location, ResourceBundle resources) { @@ -61,9 +61,9 @@ public class FinishScreenViewController implements Initializable { finishOrderTable.refresh(); } - public void setFinishers (List participants) { - List sorted = new ArrayList<>(participants); - sorted.sort(Comparator.comparingInt(Yacht::getPositionInteger)); + public void setFinishers(List participants) { + List sorted = new ArrayList<>(participants); + sorted.sort(Comparator.comparingInt(ClientYacht::getPositionInteger)); finishOrderTable.getItems().setAll(sorted); } diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index 3fe3ddad..3175ed05 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -34,8 +34,8 @@ import javafx.scene.text.Text; import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.util.StringConverter; +import seng302.model.ClientYacht; import seng302.model.RaceState; -import seng302.model.Yacht; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.model.stream.xml.parser.RaceXMLData; @@ -70,18 +70,16 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel @FXML private Button selectAnnotationBtn; @FXML - private ComboBox yachtSelectionComboBox; + private ComboBox yachtSelectionComboBox; @FXML private Text fpsDisplay; - //Race Data - private Map participants; + private Map participants; private Map markers; private RaceXMLData courseData; private GameView gameView; private RaceState raceState; - private Timeline timerTimeline; private Timer timer = new Timer(); private List> sparkLineData = new ArrayList<>(); private ImportantAnnotationsState importantAnnotations; @@ -103,7 +101,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } public void loadRace ( - Map participants, RaceXMLData raceData, RaceState raceState, Yacht player + Map participants, RaceXMLData raceData, RaceState raceState, + ClientYacht player ) { this.participants = participants; this.courseData = raceData; @@ -211,13 +210,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel /** - * Used to add any new yachts into the race that may have started late or not have had data received yet + * Used to add any new yachts into the race that may have started late or not have had data + * received yet */ - private void updateSparkLine(){ + private void updateSparkLine() { // TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed. // Collect the racing yachts that aren't already in the chart sparkLineData.clear(); - List sparkLineCandidates = new ArrayList<>(participants.values()); + List sparkLineCandidates = new ArrayList<>(participants.values()); // Create a new data series for new yachts sparkLineCandidates .stream() @@ -231,14 +231,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel 1.0 + participants.size() - yacht.getPositionInteger() ) ); - sparkLineData.add(yachtData); + sparkLineData.add(yachtData); }); // Lambda function to sort the series in order of leg (later legs shown more to the right) sparkLineData.sort((o1, o2) -> { - Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue()); - Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue()); - if (leg2 < leg1){ + Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size() - 1).getXValue()); + Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size() - 1).getXValue()); + if (leg2 < leg1) { return 1; } else { return -1; @@ -251,7 +251,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel .filter(spark -> !raceSparkLine.getData().contains(spark)) .forEach(spark -> { raceSparkLine.getData().add(spark); - spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName())); + spark.getNode().lookup(".chart-series-line") + .setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName())); }) ); } @@ -263,15 +264,15 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel /** * Updates the yachts sparkline of the desired yacht and using the new leg number - * @param yacht The yacht to be updated on the sparkline + * @param clientYacht The yacht to be updated on the sparkline * @param legNumber the leg number that the position will be assigned to */ - void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){ + void updateYachtPositionSparkline(ClientYacht clientYacht, Integer legNumber) { for (XYChart.Series positionData : sparkLineData) { positionData.getData().add( new Data<>( Integer.toString(legNumber), - 1.0 + participants.size() - yacht.getPositionInteger() + 1.0 + participants.size() - clientYacht.getPositionInteger() ) ); } @@ -287,26 +288,27 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel /** * gets the rgb string of the yachts colour to use for the chart via css + * * @param yachtId id of yacht passed in to get the yachts colour * @return the colour as an rgb string */ - private String getBoatColorAsRGB(String yachtId){ + private String getBoatColorAsRGB(String yachtId) { Color color = participants.get(Integer.valueOf(yachtId)).getColour(); - if (color == null){ - return String.format("#%02X%02X%02X",255,255,255); + if (color == null) { + return String.format("#%02X%02X%02X", 255, 255, 255); } - return String.format( "#%02X%02X%02X", - (int)( color.getRed() * 255 ), - (int)( color.getGreen() * 255 ), - (int)( color.getBlue() * 255 ) + return String.format("#%02X%02X%02X", + (int) (color.getRed() * 255), + (int) (color.getGreen() * 255), + (int) (color.getBlue() * 255) ); } /** * Initialises a timer which updates elements of the RaceView such as wind direction, yacht - * orderings etc.. which are dependent on the info from the stream parser constantly. - * Updates of each of these attributes are called ONCE EACH SECOND + * orderings etc.. which are dependent on the info from the stream parser constantly. Updates of + * each of these attributes are called ONCE EACH SECOND */ private void initializeUpdateTimer() { timer.scheduleAtFixedRate(new TimerTask() { @@ -321,9 +323,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } /** - * Iterates over all corners until ones SeqID matches with the yachts current leg number. - * Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark - * Returns null if no next mark found. + * Iterates over all corners until ones SeqID matches with the yachts current leg number. Then + * it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark Returns + * null if no next mark found. + * * @param bg The BoatGroup to find the next mark of * @return The next Mark or null if none found */ @@ -379,26 +382,25 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel // positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); // list of racing yacht id - List sorted = new ArrayList<>(participants.values()); - sorted.sort(Comparator.comparingInt(Yacht::getPositionInteger)); + List sorted = new ArrayList<>(participants.values()); + sorted.sort(Comparator.comparingInt(ClientYacht::getPositionInteger)); List vboxEntries = new ArrayList<>(); - for (Yacht yacht : sorted) { + for (ClientYacht clientYacht : sorted) { // System.out.println("yacht == null " + String.valueOf(yacht == null)); - if (yacht.getBoatStatus() == 3) { // 3 is finish status - Text textToAdd = new Text(yacht.getPositionInteger() + ". " + - yacht.getShortName() + " (Finished)"); + if (clientYacht.getBoatStatus() == 3) { // 3 is finish status + Text textToAdd = new Text(clientYacht.getPositionInteger() + ". " + + clientYacht.getShortName() + " (Finished)"); textToAdd.setFill(Paint.valueOf("#d3d3d3")); vboxEntries.add(textToAdd); } else { - Text textToAdd = new Text(yacht.getPositionInteger() + ". " + - yacht.getShortName() + " "); + Text textToAdd = new Text(clientYacht.getPositionInteger() + ". " + + clientYacht.getShortName() + " "); textToAdd.setFill(Paint.valueOf("#d3d3d3")); textToAdd.setStyle(""); vboxEntries.add(textToAdd); } -// System.out.println("finished a loop :))))))))))))"); } Platform.runLater(() -> positionVbox.getChildren().setAll(vboxEntries) @@ -478,15 +480,17 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } - private Point2D getPointRotation(Point2D ref, Double distance, Double angle){ - Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle); - Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle); + private Point2D getPointRotation(Point2D ref, Double distance, Double angle) { + Double newX = ref.getX() + (ref.getX() + distance - ref.getX()) * Math.cos(angle) + - (ref.getY() + distance - ref.getY()) * Math.sin(angle); + Double newY = ref.getY() + (ref.getX() + distance - ref.getX()) * Math.sin(angle) + + (ref.getY() + distance - ref.getY()) * Math.cos(angle); return new Point2D(newX, newY); } - public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) { + public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) { Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle); Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY()); @@ -505,8 +509,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel /** - * Initialised the combo box with any yachts currently in the race and adds the required listener - * for the combobox to take action upon selection + * Initialised the combo box with any yachts currently in the race and adds the required + * listener for the combobox to take action upon selection */ private void initialiseBoatSelectionComboBox() { yachtSelectionComboBox.setItems( @@ -543,7 +547,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel TimeUnit.MILLISECONDS.toHours(milliseconds), TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60, //Modulus 60 minutes per hour TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60 //Modulus 60 seconds per minute - ); + ); } private void setAnnotations(Integer annotationLevel) { @@ -578,9 +582,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel /** * Sets all the annotations of the selected yacht to be visible and all others to be hidden * - * @param yacht The yacht for which we want to view all annotations + * @param clientYacht The yacht for which we want to view all annotations */ - private void setSelectedBoat(Yacht yacht) { + private void setSelectedBoat(ClientYacht clientYacht) { // for (BoatObject bg : gameViewController.getBoatGroups()) { // //We need to iterate over all race groups to get the matching yacht group belonging to this yacht if we // //are to toggle its annotations, there is no other backwards knowledge of a yacht to its yachtgroup. @@ -594,11 +598,22 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel // } } - public void updateRaceData (RaceXMLData raceData) { + public void updateRaceData(RaceXMLData raceData) { this.courseData = raceData; gameView.updateBorder(raceData.getCourseLimit()); } + /** + * Called by game client after receiving yacht event packet. Parameter subject id is the + * offending yacht. This function in turn will pass the yacht location to game view to display a + * collision alert. + * + * @param subjectId source id of offending yacht + */ + public void showCollision(Long subjectId) { + gameView.drawCollision(participants.get((int) (long) subjectId).getLocation()); + } + public GameView getGameView() { return gameView; } diff --git a/src/main/resources/config/teams.xml b/src/main/resources/config/teams.xml index 0ac01cac..42e344c0 100644 --- a/src/main/resources/config/teams.xml +++ b/src/main/resources/config/teams.xml @@ -4,37 +4,37 @@ Oracle Team USA USA - 0.0 + 0.0 102 Artemis Racing ART - 0.0 + 0.0 101 Emirates Team New Zealand NZL - 0.0 + 0.0 103 Land Rover BAR BAR - 0.0 + 0.0 104 SoftBank Team Japan JAP - 0.0 + 0.0 105 Groupama Team France FRC - 0.0 + 0.0 106 \ No newline at end of file diff --git a/src/main/resources/server_config/boats1.xml b/src/main/resources/server_config/boats1.xml deleted file mode 100644 index 401e7bf6..00000000 --- a/src/main/resources/server_config/boats1.xml +++ /dev/null @@ -1,171 +0,0 @@ - - - 2015-08-28T17:32:59+0100 - 12 - 219 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/server_config/boats2.xml b/src/main/resources/server_config/boats2.xml deleted file mode 100644 index c7255771..00000000 --- a/src/main/resources/server_config/boats2.xml +++ /dev/null @@ -1,161 +0,0 @@ - - - 2015-08-28T17:32:59+0100 - 12 - 219 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/server_config/boats3.xml b/src/main/resources/server_config/boats3.xml deleted file mode 100644 index 401e7bf6..00000000 --- a/src/main/resources/server_config/boats3.xml +++ /dev/null @@ -1,171 +0,0 @@ - - - 2015-08-28T17:32:59+0100 - 12 - 219 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/views/StartScreenView.fxml b/src/main/resources/views/StartScreenView.fxml index ec629307..4bd0a808 100644 --- a/src/main/resources/views/StartScreenView.fxml +++ b/src/main/resources/views/StartScreenView.fxml @@ -1,5 +1,10 @@ + + + + + @@ -10,6 +15,7 @@ + @@ -25,7 +31,7 @@ - + diff --git a/src/test/java/seng302/ColorsTest.java b/src/test/java/seng302/ColorsTest.java index d9f1ee4f..0b4b4238 100644 --- a/src/test/java/seng302/ColorsTest.java +++ b/src/test/java/seng302/ColorsTest.java @@ -9,9 +9,9 @@ public class ColorsTest { @Test public void testNextColor() { - Color expectedColors[] = {Color.RED, Color.PERU, Color.SEAGREEN, Color.GREEN, Color.BLUE, Color.PURPLE}; - for (int i = 0; i<6; i++) - { + Color expectedColors[] = {Color.RED, Color.PERU, Color.GOLD, Color.GREEN, Color.BLUE, + Color.PURPLE, Color.DEEPPINK, Color.GRAY}; + for (int i = 0; i < 8; i++) { Assert.assertEquals(expectedColors[i], Colors.getColor()); } } diff --git a/src/test/java/seng302/model/UpdateYachtTest.java b/src/test/java/seng302/model/UpdateYachtTest.java new file mode 100644 index 00000000..4276aa93 --- /dev/null +++ b/src/test/java/seng302/model/UpdateYachtTest.java @@ -0,0 +1,67 @@ +package seng302.model; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import seng302.gameServer.GameState; +import seng302.utilities.GeoUtility; + +/** + * Test update function in Yacht.java to make sure yacht will not be collide each other within 25.0 + * meters. + */ +public class UpdateYachtTest { + + private ServerYacht yacht1 = new ServerYacht("Yacht", 1, "1", "Yacht" + 1, "Yacht" + 1, "Test1"); + private ServerYacht yacht2 = new ServerYacht("Yacht", 2, "2", "Yacht" + 2, "Yacht" + 2, "Test2"); + private GeoPoint geoPoint1 = new GeoPoint(50.0, 50.0); + private GeoPoint geoPoint2 = GeoUtility.getGeoCoordinate(geoPoint1, 90.0, 50.0); + + @Before + public void setUpRace() { + new GameState(""); + GameState.addYacht(1, yacht1); + GameState.addYacht(2, yacht2); + PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); + } + + @Test + public void testUpdateYachtWithCollision() { + // Yacht 1 heading towards 90 degrees heading + yacht1.setLocation(geoPoint1); + + // Yacht 2 heading towards 270 degrees heading + yacht2.setLocation(geoPoint1); + + // Start yacht 1 and rest yacht 2 + if (!yacht1.getSailIn()) { + yacht1.toggleSailIn(); + } + GameState.checkForCollision(yacht1); + double moved = GeoUtility.getDistance(yacht1.getLocation(), geoPoint1); + Assert.assertEquals(GameState.BOUNCE_DISTANCE_YACHT, moved, 0.1); + } + + @Test + public void testUpdateYachtWithoutCollision() { + // Yacht 1 heading towards 90 degrees heading + yacht1.setLocation(geoPoint1); + + // Yacht 2 heading towards 270 degrees heading + yacht2.setLocation(geoPoint2); + + // Start yacht 1 and rest yacht 2 + if (!yacht1.getSailIn()) { + yacht1.toggleSailIn(); + } + GameState.checkForCollision(yacht1); + Assert.assertTrue( + GameState.YACHT_COLLISION_DISTANCE < GeoUtility.getDistance(geoPoint1, geoPoint2 + ) + ); //Check that yachts are actually far enough apart for no collision. + Assert.assertEquals(geoPoint1.getLat(), yacht1.getLocation().getLat(), 0.001); + Assert.assertEquals(geoPoint1.getLng(), yacht1.getLocation().getLng(), 0.001); + Assert.assertEquals(geoPoint2.getLat(), yacht1.getLocation().getLat(), 0.001); + Assert.assertEquals(geoPoint2.getLng(), yacht1.getLocation().getLng(), 0.001); + } +} diff --git a/src/test/java/seng302/model/mark/CompoundMarkTest.java b/src/test/java/seng302/model/mark/CompoundMarkTest.java index 83b2b2bb..90d5c099 100644 --- a/src/test/java/seng302/model/mark/CompoundMarkTest.java +++ b/src/test/java/seng302/model/mark/CompoundMarkTest.java @@ -26,8 +26,8 @@ public class CompoundMarkTest { @Before public void setUp() throws Exception { - mark1 = new Mark("Mark1", 57.670333, 11.842833, 0); - mark2 = new Mark("Mark2", 57.671524, 11.844495, 1); + mark1 = new Mark("Mark1", 1, 57.670333, 11.842833, 0); + mark2 = new Mark("Mark2", 2, 57.671524, 11.844495, 1); List gateMarks = new ArrayList(); gateMarks.add(mark1); diff --git a/src/test/java/seng302/models/YachtTest.java b/src/test/java/seng302/models/YachtTest.java index 49b73daa..788ae1dc 100644 --- a/src/test/java/seng302/models/YachtTest.java +++ b/src/test/java/seng302/models/YachtTest.java @@ -8,12 +8,12 @@ import org.junit.BeforeClass; import org.junit.Test; import seng302.gameServer.GameState; import seng302.model.PolarTable; -import seng302.model.Yacht; +import seng302.model.ServerYacht; public class YachtTest { - private static Yacht y1; + private static ServerYacht y1; //Yacht y2; private static Double windDirection = 180d; private static Double windSpeed = 20d; @@ -21,7 +21,7 @@ public class YachtTest { @BeforeClass public static void setUp() { - y1 = new Yacht("Yacht", 101, "Y1", "Y1", "Yacht 1", "C1"); + y1 = new ServerYacht("Yacht", 101, "Y1", "Y1", "Yacht 1", "C1"); gs = new GameState("localhost"); } diff --git a/src/test/java/seng302/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java index ca2af0e9..a9a60148 100644 --- a/src/test/java/seng302/utilities/GeoUtilityTest.java +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -177,4 +177,16 @@ public class GeoUtilityTest { assertEquals(57.6709285, result.getLat(), result.getLat() * toleranceRate); assertEquals(11.836164, result.getLng(), result.getLng() * toleranceRate); } + + @Test + public void testKnotsToMMS() { + Double result = GeoUtility.knotsToMMS(1.94384); + assertEquals(1000, result, result * toleranceRate); + } + + @Test + public void testMMSToKnots() { + Double result = GeoUtility.mmsToKnots(1000.0); + assertEquals(1.94384, result, result * toleranceRate); + } } \ No newline at end of file diff --git a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java index d8c09728..d7e2610e 100644 --- a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java +++ b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java @@ -1,15 +1,9 @@ package seng302.visualiser.ClientToServerTests; -import java.util.ArrayList; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import seng302.gameServer.GameStages; -import seng302.gameServer.GameState; import seng302.gameServer.MainServerThread; -import seng302.gameServer.server.messages.BoatAction; -import seng302.model.Yacht; import seng302.visualiser.ClientToServerThread; /** @@ -22,31 +16,32 @@ public class RegularPacketsTest { @Before public void setup() throws Exception { - new GameState("localhost"); - serverThread = new MainServerThread(); - clientThread = new ClientToServerThread("localhost", 4942); - GameState.setCurrentStage(GameStages.RACING); +// new GameState("localhost"); +// serverThread = new MainServerThread(); +// clientThread = new ClientToServerThread("localhost", 4942); +// GameState.setCurrentStage(GameStages.RACING); } @Test public void packetsSentAtRegularIntervals () throws Exception { - final double TEST_DISTANCE = 10.0; - serverThread.startGame(); - SleepThreadMaxDelay(); - Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); - double startAngle = yacht.getHeading(); - long startTime = System.currentTimeMillis(); - clientThread.sendBoatAction(BoatAction.UPWIND); - Thread.sleep(200); - while (Math.abs(yacht.getHeading() - startAngle) < TEST_DISTANCE) { - Thread.sleep(1); - } - clientThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); - long endTime = System.currentTimeMillis(); - SleepThreadMaxDelay(); - //Allowed to be two loops of delay due to loop delay and processing delay at client + server ends. - Assert.assertEquals(TEST_DISTANCE / Yacht.TURN_STEP * ClientToServerThread.PACKET_SENDING_INTERVAL_MS, - (endTime - startTime), 2 * ClientToServerThread.PACKET_SENDING_INTERVAL_MS); +// final double TEST_DISTANCE = 10.0; +// serverThread.startGame(); +// SleepThreadMaxDelay(); +// ServerYacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); +// double startAngle = yacht.getHeading(); +// long startTime = System.currentTimeMillis(); +// clientThread.sendBoatAction(BoatAction.UPWIND); +// Thread.sleep(200); +// while (Math.abs(yacht.getHeading() - startAngle) < TEST_DISTANCE) { +// Thread.sleep(1); +// } +// clientThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); +// long endTime = System.currentTimeMillis(); +// SleepThreadMaxDelay(); +// //Allowed to be two loops of delay due to loop delay and processing delay at client + server ends. +// Assert.assertEquals( +// TEST_DISTANCE / ServerYacht.TURN_STEP * ClientToServerThread.PACKET_SENDING_INTERVAL_MS, +// (endTime - startTime), 2 * ClientToServerThread.PACKET_SENDING_INTERVAL_MS); } // @Test @@ -71,10 +66,10 @@ public class RegularPacketsTest { @After public void teardown () throws Exception { - clientThread.setSocketToClose(); - serverThread.terminate(); - GameState.setCurrentStage(GameStages.LOBBYING); - for (int i = 0; i<20; i++) - SleepThreadMaxDelay(); //Make sure socket is closed and toolkit remade. +// clientThread.setSocketToClose(); +// serverThread.terminate(); +// GameState.setCurrentStage(GameStages.LOBBYING); +// for (int i = 0; i<20; i++) +// SleepThreadMaxDelay(); //Make sure socket is closed and toolkit remade. } } diff --git a/src/test/java/seng302/visualiser/map/BoatSailAnimationToggleTest.java b/src/test/java/seng302/visualiser/map/BoatSailAnimationToggleTest.java index cccea5c6..be2f7a46 100644 --- a/src/test/java/seng302/visualiser/map/BoatSailAnimationToggleTest.java +++ b/src/test/java/seng302/visualiser/map/BoatSailAnimationToggleTest.java @@ -1,31 +1,29 @@ package seng302.visualiser.map; import static junit.framework.TestCase.assertFalse; -import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertTrue; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import seng302.model.Yacht; -import seng302.visualiser.fxObjects.BoatObject; +import seng302.model.ClientYacht; /** * Created by kre39 on 6/08/17. */ public class BoatSailAnimationToggleTest { - private Yacht yacht; + private ClientYacht yacht; @Before public void setup() throws Exception{ - yacht = new Yacht("Yacht", 1, "YACHT", "YAC", "Test Yacht", "NZ"); + yacht = new ClientYacht("Yacht", 1, "YACHT", "YAC", "Test Yacht", "NZ"); } @Test public void sailToggleTest() throws Exception { assertFalse(yacht.getSailIn()); - yacht.toggleClientSail(); - assertFalse(yacht.getSailIn()); + yacht.toggleSail(); + assertTrue(yacht.getSailIn()); } } diff --git a/src/test/java/steps/ToggleSailSteps.java b/src/test/java/steps/ToggleSailSteps.java index 66297c87..f74b05be 100644 --- a/src/test/java/steps/ToggleSailSteps.java +++ b/src/test/java/steps/ToggleSailSteps.java @@ -3,53 +3,51 @@ package steps; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; +import java.util.ArrayList; import org.junit.Assert; import seng302.gameServer.GameStages; import seng302.gameServer.GameState; import seng302.gameServer.MainServerThread; -import seng302.gameServer.server.messages.BoatActionMessage; -import seng302.model.Yacht; +import seng302.gameServer.server.messages.BoatAction; +import seng302.model.ServerYacht; import seng302.visualiser.ClientToServerThread; -import java.util.ArrayList; -import java.util.Collections; - /** * Created by kre39 on 7/08/17. */ public class ToggleSailSteps { - MainServerThread mst; ClientToServerThread client; - boolean sailsIn = false; long startTime; - private Yacht yacht; - - @Given("^The game is running$") public void the_game_is_running() throws Throwable { mst = new MainServerThread(); client = new ClientToServerThread("localhost", 4942); GameState.setCurrentStage(GameStages.RACING); + Thread.sleep(200); // Sleep needed to help the threads all be up to speed with each other + ServerYacht yacht = (new ArrayList<>(GameState.getYachts().values())).get(0); + Assert.assertFalse(yacht.getSailIn()); } + @When("^the user has pressed \"([^\"]*)\"$") public void the_user_has_pressed(String arg1) throws Throwable { startTime = System.currentTimeMillis(); if (arg1 == "shift") { - if (sailsIn) { -// client.sendBoatActionMessage(new BoatActionMessage(BoatActionType.SAILS_OUT)); - } else { -// client.sendBoatActionMessage(new BoatActionMessage(BoatActionType.SAILS_IN)); - } + client.sendBoatAction(BoatAction.SAILS_IN); } } @Then("^the sails are \"([^\"]*)\"$") public void the_sails_are(String arg1) throws Throwable { - //Yacht yacht = (new ArrayList<>(GameState.getYachts().values())).get(0); - //Assert.assertTrue(yacht.getSailIn()); + Thread.sleep(200); // Sleep needed to help the threads all be up to speed with each other + ServerYacht yacht = (new ArrayList<>(GameState.getYachts().values())).get(0); + if (arg1 == "in") { + Assert.assertTrue(yacht.getSailIn()); + } else { + Assert.assertFalse(yacht.getSailIn()); + } } }