mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 06:18:44 +00:00
Refactor. Taken Rounding logic out of yacht and into game state.
tags: #story[1124]
This commit is contained in:
@@ -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.
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
|
||||
@@ -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<Integer, Yacht> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,37 +4,37 @@
|
||||
<team>
|
||||
<name>Oracle Team USA</name>
|
||||
<alias>USA</alias>
|
||||
<velocity>0.0</velocity>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>102</id>
|
||||
</team>
|
||||
<team>
|
||||
<name>Artemis Racing</name>
|
||||
<alias>ART</alias>
|
||||
<velocity>0.0</velocity>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>101</id>
|
||||
</team>
|
||||
<team>
|
||||
<name>Emirates Team New Zealand</name>
|
||||
<alias>NZL</alias>
|
||||
<velocity>0.0</velocity>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>103</id>
|
||||
</team>
|
||||
<team>
|
||||
<name>Land Rover BAR</name>
|
||||
<alias>BAR</alias>
|
||||
<velocity>0.0</velocity>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>104</id>
|
||||
</team>
|
||||
<team>
|
||||
<name>SoftBank Team Japan</name>
|
||||
<alias>JAP</alias>
|
||||
<velocity>0.0</velocity>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>105</id>
|
||||
</team>
|
||||
<team>
|
||||
<name>Groupama Team France</name>
|
||||
<alias>FRC</alias>
|
||||
<velocity>0.0</velocity>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>106</id>
|
||||
</team>
|
||||
</teams>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user