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 85f7393c..3c3ec2c6 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -8,27 +8,35 @@ import seng302.gameServer.server.messages.BoatAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.model.GeoPoint; import seng302.model.Player; +import seng302.model.PolarTable; import seng302.model.Yacht; +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 { @FunctionalInterface interface MarkPassingListener { - void markPassing(Message message); } private Logger logger = LoggerFactory.getLogger(GameState.class); - private static Integer STATE_UPDATES_PER_SECOND = 60; + 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 private static Long previousUpdateTime; public static Double windDirection; @@ -135,56 +143,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() { 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() { @@ -199,7 +168,7 @@ public class GameState implements Runnable { @Override public void run() { - while(true) { + while (true) { try { Thread.sleep(1000 / STATE_UPDATES_PER_SECOND); } catch (InterruptedException e) { @@ -209,28 +178,317 @@ 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) { + Yacht 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() { + Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0; + previousUpdateTime = System.currentTimeMillis(); + for (Yacht yacht : yachts.values()) { + updateVelocity(yacht); + yacht.runAutoPilot(); + yacht.updateLocation(timeInterval); + if (!yacht.getFinishedRace()) { + checkForLegProgression(yacht); + } + } + } + + + private void updateVelocity(Yacht yacht) { + Double velocity = yacht.getCurrentVelocity(); + Double trueWindAngle = Math.abs(windDirection - yacht.getHeading()); + Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle); + Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots); + yacht.setCurrentMaxVelocity(maxBoatSpeed); + + if (yacht.getSailIn() && yacht.getCurrentVelocity() <= maxBoatSpeed && maxBoatSpeed != 0d) { + if (velocity < maxBoatSpeed) { + yacht.changeVelocity(maxBoatSpeed / 15); + } + if (velocity > maxBoatSpeed) { + yacht.setCurrentVelocity(maxBoatSpeed); + } + } else { + if (velocity > 0d) { + if (maxBoatSpeed != 0d) { + yacht.changeVelocity(-maxBoatSpeed / 600); + } else { + yacht.changeVelocity(-velocity / 100); + } + if (velocity < 0) { + 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(Yacht 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(Yacht yacht) { + Integer currentMarkSeqID = yacht.getCurrentMarkSeqID(); + CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID); + + 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(Yacht 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); + 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(Yacht 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(Yacht 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(Yacht 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.setIsFinished(true); + logger.debug(yacht.getSourceId() + " finished"); + return true; + } + } + + return false; + } + + private void sendMarkRoundingMessage(Yacht 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()); + + for (MarkPassingListener mpl : markListeners) { + mpl.markPassing(markRoundingMessage); + } + } + + + private void logMarkRounding(Yacht yacht) { + Mark roundingMark = yacht.getClosestCurrentMark(); + + logger.debug( + String.format("Sending Mark Rounding Message:\n" + + "AckNumber %d\n" + + "RaceID %d\n" + + "BoatSourceID %d\n" + + "BoatStatus %s\n" + + "Rounding Side %s\n" + + "MarkSeqID %d", + 0, + 0, + yacht.getSourceId(), + RoundingBoatStatus.RACING.name(), + roundingMark.getRoundingSide().getName(), + roundingMark.getSourceID())); + } + + public static void addMarkPassListener(MarkPassingListener listener) { markListeners.add(listener); } - - public static void removeMarkPassListenr(MarkPassingListener listener) { - markListeners.remove(listener); - } } diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index 306d234a..65ca99dd 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -34,6 +34,7 @@ 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.start(); @@ -89,7 +90,7 @@ public class MainServerThread extends Observable implements Runnable, ClientConn } } - public void broadcastMessage(Message message) { + private void broadcastMessage(Message message) { for (ServerToClientThread serverToClientThread : serverToClientThreads) { serverToClientThread.sendMessage(message); } diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 06ed2c0a..a5ae3257 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -308,7 +308,7 @@ public class ServerToClientThread implements Runnable, Observer { yacht.getLocation().getLat(), yacht.getLocation().getLng(), yacht.getHeading(), - yacht.getVelocity().longValue()); + yacht.getCurrentVelocity().longValue()); sendMessage(boatLocationMessage); } diff --git a/src/main/java/seng302/gameServer/server/messages/MarkRoundingMessage.java b/src/main/java/seng302/gameServer/server/messages/MarkRoundingMessage.java index 7820f49a..f0d68e15 100644 --- a/src/main/java/seng302/gameServer/server/messages/MarkRoundingMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/MarkRoundingMessage.java @@ -20,7 +20,7 @@ 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 diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 4551ade4..ba967dfe 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -15,11 +15,6 @@ import javafx.scene.paint.Color; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import seng302.gameServer.GameState; -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.RoundingSide; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.utilities.GeoUtility; @@ -39,8 +34,6 @@ public class Yacht extends Observable { 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; @@ -64,7 +57,8 @@ public class Yacht extends Observable { private Boolean sailIn; private GeoPoint location; private Integer boatStatus; - private Double velocity; + private Double currentVelocity; + private Double currentMaxVelocity; private Boolean isAuto; private Double autoHeading; @@ -98,7 +92,7 @@ public class Yacht extends Observable { this.location = new GeoPoint(57.670341, 11.826856); this.lastLocation = location; this.heading = 120.0; //In degrees - this.velocity = 0d; //in mms-1 + this.currentVelocity = 0d; //in mms-1 this.hasEnteredRoundingZone = false; this.hasPassedLine = false; @@ -106,51 +100,23 @@ public class Yacht extends Observable { this.finishedRace = false; } + /** - * @param timeInterval since last update in milliseconds + * 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 update(Long timeInterval) { + public void changeVelocity(Double velocityChange) { + currentVelocity += velocityChange; + } - 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 + /** + * 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, velocity * secondsElapsed); - - //CHECK FOR MARK ROUNDING - if (!finishedRace) { - checkForLegProgression(); - } - - // TODO: 3/08/17 wmu16 - Implement line cross check here + location = GeoUtility.getGeoCoordinate(location, heading, currentVelocity * secondsElapsed); } /** @@ -162,197 +128,6 @@ public class Yacht extends Observable { super.addObserver(o); } - private void sendMarkRoundingMessage() { - CompoundMark currentMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID); - MarkType markType = (currentMark.isGate()) ? MarkType.GATE : MarkType.ROUNDING_MARK; - - // 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, closestCurrentMark.getRoundingSide(), markType, - closestCurrentMark.getSourceID()); - setChanged(); - notifyObservers(markRoundingMessage); - logMarkRounding(currentMark); - - hasPassedLine = false; - hasEnteredRoundingZone = false; - hasPassedThroughGate = false; - currentMarkSeqID++; - } - - private void logMarkRounding(CompoundMark currentMark) { - logger.debug( - String.format("Sending Mark Rounding Message:\n" - + "AckNumber %d\n" - + "RaceID %d\n" - + "BoatSourceID %d\n" - + "BoatStatus %s\n" - + "Rounding Side %s\n" - + "MarkSeqID %d", - 0, - 0, - sourceId, - RoundingBoatStatus.RACING.name(), - closestCurrentMark.getRoundingSide().getName(), - currentMark.getSubMark(1).getSourceID())); - } - - /** - * 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); - if (distance1 < distance2) { - closestCurrentMark = sub1; - return distance1; - } else { - closestCurrentMark = sub2; - return distance2; - } - } else { - closestCurrentMark = nextMark.getSubMark(1); - 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) { - closestCurrentMark = mark1; - sendMarkRoundingMessage(); - } - } - } - - - /** - * 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) { - sendMarkRoundingMessage(); - } - } - - - /** - * 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 { - sendMarkRoundingMessage(); - } - } - } - - /** - * 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) { - closestCurrentMark = mark1; - sendMarkRoundingMessage(); - finishedRace = true; - logger.debug(sourceId + " finished"); - } - } - } - /** * Adjusts the heading of the boat by a given amount, while recording the boats last heading. @@ -603,7 +378,7 @@ public class Yacht extends Observable { } public double getVelocityMMS() { - return velocity; + return currentVelocity; } public ReadOnlyLongProperty timeTillNextProperty() { @@ -611,7 +386,7 @@ public class Yacht extends Observable { } public Double getVelocityKnots() { - return velocity / 1000 * 1.943844492; // TODO: 26/07/17 cir27 - remove magic number + return currentVelocity / 1000 * 1.943844492; // TODO: 26/07/17 cir27 - remove magic number } public Long getTimeTillNext() { @@ -685,23 +460,82 @@ public class Yacht extends Observable { this.colour = colour; } - - public Double getVelocity() { - return velocity; + public void setIsFinished(Boolean isFinished) { + finishedRace = isFinished; } - public void setVelocity(Double velocity) { - this.velocity = velocity; + public Boolean getFinishedRace() { + return finishedRace; + } + + 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 void incrementMarkSeqID() { + currentMarkSeqID++; + } + + public Boolean hasEnteredRoundingZone() { + return hasEnteredRoundingZone; + } + + public Boolean hasPassedThroughGate() { + return hasPassedThroughGate; + } + + public Boolean hasPassedLine() { + return hasPassedLine; } public Double getDistanceToCurrentMark() { return distanceToCurrentMark; } + public Double getCurrentMaxVelocity() { + return currentMaxVelocity; + } + + public void setCurrentMaxVelocity(Double currentMaxVelocity) { + this.currentMaxVelocity = currentMaxVelocity; + } + public void updateLocation(double lat, double lng, double heading, double velocity) { setLocation(lat, lng); this.heading = heading; - this.velocity = velocity; + this.currentVelocity = velocity; updateVelocityProperty(velocity); for (YachtLocationListener yll : locationListeners) { yll.notifyLocation(this, lat, lng, heading, velocity); 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/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/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