Refactor. Taken Rounding logic out of yacht and into game state.

tags: #story[1124]
This commit is contained in:
William Muir
2017-08-14 23:52:06 +12:00
parent e1dddd317d
commit baacd8a9c0
10 changed files with 439 additions and 317 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ prints out event details, including time, involved boats and legs.
- Configuration file - 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. 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. By using this library, we did not have to write our json parser and benefited from the flexibility of json files.
+1 -1
View File
@@ -8,7 +8,7 @@ You can specify a config file using the using the -f flag, for example 'java -ja
## The config file ## 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. 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.
+315 -57
View File
@@ -8,27 +8,35 @@ import seng302.gameServer.server.messages.BoatAction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import seng302.gameServer.server.messages.MarkRoundingMessage; import seng302.gameServer.server.messages.MarkRoundingMessage;
import seng302.gameServer.server.messages.MarkType;
import seng302.gameServer.server.messages.Message; import seng302.gameServer.server.messages.Message;
import seng302.gameServer.server.messages.RoundingBoatStatus;
import seng302.model.GeoPoint;
import seng302.model.Player; import seng302.model.Player;
import seng302.model.PolarTable;
import seng302.model.Yacht; import seng302.model.Yacht;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkOrder; import seng302.model.mark.MarkOrder;
import seng302.utilities.GeoUtility;
/** /**
* A Static class to hold information about the current state of the game (model) * 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. * Created by wmu16 on 10/07/17.
*/ */
public class GameState implements Runnable { public class GameState implements Runnable {
@FunctionalInterface @FunctionalInterface
interface MarkPassingListener { interface MarkPassingListener {
void markPassing(Message message); void markPassing(Message message);
} }
private Logger logger = LoggerFactory.getLogger(GameState.class); 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 Integer MAX_PLAYERS = 8;
public static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
private static Long previousUpdateTime; private static Long previousUpdateTime;
public static Double windDirection; public static Double windDirection;
@@ -135,56 +143,17 @@ public class GameState implements Runnable {
} }
public static Double getWindSpeedKnots() { 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<Integer, Yacht> getYachts() { public static Map<Integer, Yacht> getYachts() {
return yachts; 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 * Generates a new ID based off the size of current players + 1
*
* @return a playerID to be allocated to a new connetion * @return a playerID to be allocated to a new connetion
*/ */
public static Integer getUniquePlayerID() { public static Integer getUniquePlayerID() {
@@ -199,7 +168,7 @@ public class GameState implements Runnable {
@Override @Override
public void run() { public void run() {
while(true) { while (true) {
try { try {
Thread.sleep(1000 / STATE_UPDATES_PER_SECOND); Thread.sleep(1000 / STATE_UPDATES_PER_SECOND);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -209,28 +178,317 @@ public class GameState implements Runnable {
update(); update();
} }
//RACING
if (currentStage == GameStages.RACING) { if (currentStage == GameStages.RACING) {
update(); update();
} }
} }
} }
private static void printBoatStatus(Yacht playerYacht) { public static void updateBoat(Integer sourceId, BoatAction actionType) {
System.out.println("-----------------------"); Yacht playerYacht = yachts.get(sourceId);
System.out.println("Sails are in: " + playerYacht.getSailIn()); switch (actionType) {
System.out.println("Heading: " + playerYacht.getHeading()); case VMG:
System.out.println("Velocity: " + playerYacht.getVelocityMMS() / 1000); playerYacht.turnToVMG();
System.out.println("Lat: " + playerYacht.getLocation().getLat()); break;
System.out.println("Lng: " + playerYacht.getLocation().getLng()); case SAILS_IN:
System.out.println("-----------------------\n"); 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) { public static void addMarkPassListener(MarkPassingListener listener) {
markListeners.add(listener); markListeners.add(listener);
} }
public static void removeMarkPassListenr(MarkPassingListener listener) {
markListeners.remove(listener);
}
} }
@@ -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); serverLog("IO error in server thread handler upon trying to make new server socket", 0);
} }
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
GameState.addMarkPassListener(this::broadcastMessage);
terminated = false; terminated = false;
thread = new Thread(this); thread = new Thread(this);
thread.start(); 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) { for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.sendMessage(message); serverToClientThread.sendMessage(message);
} }
@@ -308,7 +308,7 @@ public class ServerToClientThread implements Runnable, Observer {
yacht.getLocation().getLat(), yacht.getLocation().getLat(),
yacht.getLocation().getLng(), yacht.getLocation().getLng(),
yacht.getHeading(), yacht.getHeading(),
yacht.getVelocity().longValue()); yacht.getCurrentVelocity().longValue());
sendMessage(boatLocationMessage); sendMessage(boatLocationMessage);
} }
@@ -20,7 +20,7 @@ public class MarkRoundingMessage extends Message{
* The purpose of this is to record the time when yachts cross marks * The purpose of this is to record the time when yachts cross marks
* @param ackNumber ackNumber * @param ackNumber ackNumber
* @param raceId raceId * @param raceId raceId
* @param sourceId sourceId * @param sourceId boatSourceId
* @param roundingBoatStatus roundingBoatStatus * @param roundingBoatStatus roundingBoatStatus
* @param roundingSide roundingSide * @param roundingSide roundingSide
* @param markId markId * @param markId markId
+83 -249
View File
@@ -15,11 +15,6 @@ import javafx.scene.paint.Color;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import seng302.gameServer.GameState; 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.CompoundMark;
import seng302.model.mark.Mark; import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility; import seng302.utilities.GeoUtility;
@@ -39,8 +34,6 @@ public class Yacht extends Observable {
private Logger logger = LoggerFactory.getLogger(Yacht.class); 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 //BOTH AFAIK
private String boatType; private String boatType;
@@ -64,7 +57,8 @@ public class Yacht extends Observable {
private Boolean sailIn; private Boolean sailIn;
private GeoPoint location; private GeoPoint location;
private Integer boatStatus; private Integer boatStatus;
private Double velocity; private Double currentVelocity;
private Double currentMaxVelocity;
private Boolean isAuto; private Boolean isAuto;
private Double autoHeading; private Double autoHeading;
@@ -98,7 +92,7 @@ public class Yacht extends Observable {
this.location = new GeoPoint(57.670341, 11.826856); this.location = new GeoPoint(57.670341, 11.826856);
this.lastLocation = location; this.lastLocation = location;
this.heading = 120.0; //In degrees this.heading = 120.0; //In degrees
this.velocity = 0d; //in mms-1 this.currentVelocity = 0d; //in mms-1
this.hasEnteredRoundingZone = false; this.hasEnteredRoundingZone = false;
this.hasPassedLine = false; this.hasPassedLine = false;
@@ -106,51 +100,23 @@ public class Yacht extends Observable {
this.finishedRace = false; 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(); * Updates the boat to a new GeoPoint whilst preserving the last location
Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading); *
Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle); * @param secondsElapsed The seconds elapsed since the last update of this yacht
Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000; */
if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) { public void updateLocation(Double secondsElapsed) {
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; lastLocation = location;
location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); location = GeoUtility.getGeoCoordinate(location, heading, currentVelocity * secondsElapsed);
//CHECK FOR MARK ROUNDING
if (!finishedRace) {
checkForLegProgression();
}
// TODO: 3/08/17 wmu16 - Implement line cross check here
} }
/** /**
@@ -162,197 +128,6 @@ public class Yacht extends Observable {
super.addObserver(o); 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. * 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() { public double getVelocityMMS() {
return velocity; return currentVelocity;
} }
public ReadOnlyLongProperty timeTillNextProperty() { public ReadOnlyLongProperty timeTillNextProperty() {
@@ -611,7 +386,7 @@ public class Yacht extends Observable {
} }
public Double getVelocityKnots() { 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() { public Long getTimeTillNext() {
@@ -685,23 +460,82 @@ public class Yacht extends Observable {
this.colour = colour; this.colour = colour;
} }
public void setIsFinished(Boolean isFinished) {
public Double getVelocity() { finishedRace = isFinished;
return velocity;
} }
public void setVelocity(Double velocity) { public Boolean getFinishedRace() {
this.velocity = velocity; 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() { public Double getDistanceToCurrentMark() {
return distanceToCurrentMark; 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) { public void updateLocation(double lat, double lng, double heading, double velocity) {
setLocation(lat, lng); setLocation(lat, lng);
this.heading = heading; this.heading = heading;
this.velocity = velocity; this.currentVelocity = velocity;
updateVelocityProperty(velocity); updateVelocityProperty(velocity);
for (YachtLocationListener yll : locationListeners) { for (YachtLocationListener yll : locationListeners) {
yll.notifyLocation(this, lat, lng, heading, velocity); yll.notifyLocation(this, lat, lng, heading, velocity);
@@ -6,6 +6,7 @@ import seng302.model.GeoPoint;
public class GeoUtility { public class GeoUtility {
private static double EARTH_RADIUS = 6378.137; 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 * Calculates the euclidean distance between two markers on the canvas using xy coordinates
@@ -234,4 +235,20 @@ public class GeoUtility {
return true; 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;
}
} }
+6 -6
View File
@@ -4,37 +4,37 @@
<team> <team>
<name>Oracle Team USA</name> <name>Oracle Team USA</name>
<alias>USA</alias> <alias>USA</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>102</id> <id>102</id>
</team> </team>
<team> <team>
<name>Artemis Racing</name> <name>Artemis Racing</name>
<alias>ART</alias> <alias>ART</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>101</id> <id>101</id>
</team> </team>
<team> <team>
<name>Emirates Team New Zealand</name> <name>Emirates Team New Zealand</name>
<alias>NZL</alias> <alias>NZL</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>103</id> <id>103</id>
</team> </team>
<team> <team>
<name>Land Rover BAR</name> <name>Land Rover BAR</name>
<alias>BAR</alias> <alias>BAR</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>104</id> <id>104</id>
</team> </team>
<team> <team>
<name>SoftBank Team Japan</name> <name>SoftBank Team Japan</name>
<alias>JAP</alias> <alias>JAP</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>105</id> <id>105</id>
</team> </team>
<team> <team>
<name>Groupama Team France</name> <name>Groupama Team France</name>
<alias>FRC</alias> <alias>FRC</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>106</id> <id>106</id>
</team> </team>
</teams> </teams>
@@ -177,4 +177,16 @@ public class GeoUtilityTest {
assertEquals(57.6709285, result.getLat(), result.getLat() * toleranceRate); assertEquals(57.6709285, result.getLat(), result.getLat() * toleranceRate);
assertEquals(11.836164, result.getLng(), result.getLng() * 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);
}
} }