Merge branch 'develop' into StartScreen

# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/utilities/StreamParser.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/controllers/LobbyController.java
This commit is contained in:
Michael Rausch
2017-08-16 14:31:14 +12:00
68 changed files with 2568 additions and 2366 deletions
@@ -0,0 +1,268 @@
package seng302.model;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.model.mark.CompoundMark;
/**
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
* not used anymore.
*/
public class ClientYacht extends Observable {
@FunctionalInterface
public interface YachtLocationListener {
void notifyLocation(ClientYacht clientYacht, double lat, double lon, double heading,
Boolean sailsIn, double velocity);
}
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
//BOTH AFAIK
private String boatType;
private Integer sourceId;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
private String boatName;
private String country;
private Long estimateTimeAtFinish;
private Boolean sailIn = false;
private Integer currentMarkSeqID = 0;
private Long markRoundTime;
private Long timeTillNext;
private Double heading;
private Integer legNumber = 0;
private GeoPoint location;
private Integer boatStatus;
private Double currentVelocity;
//CLIENT SIDE
private List<YachtLocationListener> locationListeners = new ArrayList<>();
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
private CompoundMark lastMarkRounded;
private Integer positionInt = 0;
private Color colour;
public ClientYacht(String boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
this.sourceId = sourceId;
this.hullID = hullID;
this.shortName = shortName;
this.boatName = boatName;
this.country = country;
this.location = new GeoPoint(57.670341, 11.826856);
this.heading = 120.0; //In degrees
this.currentVelocity = 0d;
this.boatStatus = 1;
}
/**
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
* rounding package.
*/
@Override
public void addObserver(Observer o) {
super.addObserver(o);
}
public String getBoatType() {
return boatType;
}
public Integer getSourceId() {
//@TODO Remove and merge with Creating Game Loop
if (sourceId == null) {
return 0;
}
return sourceId;
}
public String getHullID() {
if (hullID == null) {
return "";
}
return hullID;
}
public String getShortName() {
return shortName;
}
public String getBoatName() {
return boatName;
}
public String getCountry() {
if (country == null) {
return "";
}
return country;
}
public Integer getBoatStatus() {
return boatStatus;
}
public void setBoatStatus(Integer boatStatus) {
this.boatStatus = boatStatus;
}
public Integer getLegNumber() {
return legNumber;
}
public void setLegNumber(Integer legNumber) {
this.legNumber = legNumber;
}
public void setEstimateTimeTillNextMark(Long estimateTimeTillNextMark) {
timeTillNext = estimateTimeTillNextMark;
}
public String getEstimateTimeAtFinish() {
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
return format.format(estimateTimeAtFinish);
}
public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) {
this.estimateTimeAtFinish = estimateTimeAtFinish;
}
public Integer getPositionInteger() {
return positionInt;
}
public void setPositionInteger(Integer position) {
this.positionInt = position;
}
public void updateVelocityProperty(double velocity) {
this.velocityProperty.set(velocity);
}
public void setMarkRoundingTime(Long markRoundingTime) {
this.markRoundTime = markRoundingTime;
}
public ReadOnlyDoubleProperty getVelocityProperty() {
return velocityProperty.getReadOnlyProperty();
}
public ReadOnlyLongProperty timeTillNextProperty() {
return timeTillNextProperty.getReadOnlyProperty();
}
public Long getTimeTillNext() {
return timeTillNext;
}
public Long getMarkRoundTime() {
return markRoundTime;
}
public CompoundMark getLastMarkRounded() {
return lastMarkRounded;
}
public void setLastMarkRounded(CompoundMark lastMarkRounded) {
this.lastMarkRounded = lastMarkRounded;
}
public GeoPoint getLocation() {
return location;
}
public void toggleSail() {
sailIn = !sailIn;
}
//// TODO: 15/08/17 asd
/**
* Sets the current location of the boat in lat and long whilst preserving the last location
*
* @param lat Latitude
* @param lng Longitude
*/
public void setLocation(Double lat, Double lng) {
location.setLat(lat);
location.setLng(lng);
}
public Double getHeading() {
return heading;
}
public void setHeading(Double heading) {
this.heading = heading;
}
@Override
public String toString() {
return boatName;
}
public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) {
this.timeSinceLastMarkProperty.set(timeSinceLastMark);
}
public ReadOnlyLongProperty timeSinceLastMarkProperty() {
return timeSinceLastMarkProperty.getReadOnlyProperty();
}
public void setTimeTillNext(Long timeTillNext) {
this.timeTillNext = timeTillNext;
}
public Color getColour() {
return colour;
}
public void setColour(Color colour) {
this.colour = colour;
}
// public Double getCurrentVelocity() {
// return currentVelocity;
// }
//
// public void setCurrentVelocity(Double currentVelocity) {
// this.currentVelocity = currentVelocity;
// }
public void updateLocation(double lat, double lng, double heading, double velocity) {
setLocation(lat, lng);
this.heading = heading;
// this.currentVelocity = velocity;
updateVelocityProperty(velocity);
for (YachtLocationListener yll : locationListeners) {
yll.notifyLocation(this, lat, lng, heading, sailIn, velocity);
}
}
public void addLocationListener(YachtLocationListener listener) {
locationListeners.add(listener);
}
public boolean getSailIn () {
return sailIn;
}
}
+2 -2
View File
@@ -6,12 +6,12 @@ import javafx.scene.paint.Color;
* Enum for generating colours.
*/
public enum Colors {
RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE;
RED, PERU, GOLD, GREEN, BLUE, PURPLE, DEEPPINK, GRAY;
static Integer index = 0;
public static Color getColor() {
if (index == 6) {
if (index == 8) {
index = 0;
}
return Color.valueOf(values()[index++].toString());
@@ -28,4 +28,9 @@ public class GeoPoint {
public void setLng(double lng) {
this.lng = lng;
}
@Override
public String toString() {
return "lat: " + lat + " lng: " + lng;
}
}
+3 -3
View File
@@ -9,11 +9,11 @@ import java.net.Socket;
public class Player {
private Socket socket;
private Yacht yacht;
private ServerYacht yacht;
private Integer lastMarkPassed;
public Player(Socket socket, Yacht yacht) {
public Player(Socket socket, ServerYacht yacht) {
this.socket = socket;
this.yacht = yacht;
}
@@ -30,7 +30,7 @@ public class Player {
this.lastMarkPassed = lastMarkPassed;
}
public Yacht getYacht() {
public ServerYacht getYacht() {
return yacht;
}
@@ -71,8 +71,6 @@ public final class PolarTable {
} catch (IOException e) {
System.out.println("[PolarTable] IO exception");
}
}
@@ -155,7 +153,6 @@ public final class PolarTable {
public static Double getClosestWindSpeedInPolar(Double thisWindSpeed) {
Double smallestDif = Double.POSITIVE_INFINITY;
Double closestWind = 0d;
for (Double polarWindSpeed : polarTable.keySet()) {
Double difference = Math.abs(polarWindSpeed - thisWindSpeed);
if (difference < smallestDif) {
@@ -0,0 +1,385 @@
package seng302.model;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.GameState;
import seng302.gameServer.server.messages.BoatStatus;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
/**
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
* not used anymore.
*/
public class ServerYacht extends Observable {
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
public static final Double TURN_STEP = 5.0;
//Boat info
private String boatType;
private Integer sourceId;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
private String boatName;
private String country;
private BoatStatus boatStatus;
//Location
private Double lastHeading;
private Boolean sailIn;
private Double heading;
private GeoPoint lastLocation;
private GeoPoint location;
private Double currentVelocity;
private Boolean isAuto;
private Double autoHeading;
//Mark Rounding
private Integer currentMarkSeqID;
private Boolean hasEnteredRoundingZone;
private Mark closestCurrentMark;
private Boolean hasPassedLine;
private Boolean hasPassedThroughGate;
public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
this.boatStatus = BoatStatus.PRESTART;
this.sourceId = sourceId;
this.hullID = hullID;
this.shortName = shortName;
this.boatName = boatName;
this.country = country;
this.sailIn = false;
this.isAuto = false;
this.location = new GeoPoint(57.670341, 11.826856);
this.lastLocation = location;
this.heading = 120.0; //In degrees
this.currentVelocity = 0d; //in mms-1
this.currentMarkSeqID = 0;
this.hasEnteredRoundingZone = false;
this.hasPassedLine = false;
this.hasPassedThroughGate = false;
}
/**
* Changes the boats current currentVelocity by a set amount, positive or negative
*
* @param velocityChange The ammount to change the currentVelocity by, in mms-1
*/
public void changeVelocity(Double velocityChange) {
currentVelocity += velocityChange;
}
/**
* Updates the boat to a new GeoPoint whilst preserving the last location
*
* @param secondsElapsed The seconds elapsed since the last update of this yacht
*/
public void updateLocation(Double secondsElapsed) {
lastLocation = location;
location = GeoUtility.getGeoCoordinate(location, heading, currentVelocity * secondsElapsed);
}
public void setLocation(GeoPoint geoPoint) {
location = geoPoint;
}
/**
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
* rounding package.
*/
@Override
public void addObserver(Observer o) {
super.addObserver(o);
}
/**
* Adjusts the heading of the boat by a given amount, while recording the boats last heading.
*
* @param amount the amount by which to adjust the boat heading.
*/
public void adjustHeading(Double amount) {
Double newVal = heading + amount;
lastHeading = heading;
heading = (double) Math.floorMod(newVal.longValue(), 360L);
}
/**
* Swaps the boats direction from one side of the wind to the other.
*/
public void tackGybe(Double windDirection) {
if (isAuto) {
disableAutoPilot();
} else {
Double normalizedHeading = normalizeHeading();
Double newVal = (-2 * normalizedHeading) + heading;
Double newHeading = (double) Math.floorMod(newVal.longValue(), 360L);
setAutoPilot(newHeading);
}
}
/**
* Enables the boats auto pilot feature, which will move the boat towards a given heading.
*
* @param thisHeading The heading to move the boat towards.
*/
private void setAutoPilot(Double thisHeading) {
isAuto = true;
autoHeading = thisHeading;
}
/**
* Disables the auto pilot function.
*/
public void disableAutoPilot() {
isAuto = false;
}
/**
* Moves the boat towards the given heading when the auto pilot was set. Disables the auto pilot
* in the event that the boat is within the range of 1 turn step of its goal.
*/
public void runAutoPilot() {
if (isAuto) {
turnTowardsHeading(autoHeading);
if (Math.abs(heading - autoHeading)
<= TURN_STEP) { //Cancel when within 1 turn step of target.
isAuto = false;
}
}
}
public void toggleSailIn() {
sailIn = !sailIn;
}
public void turnUpwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
} else if (normalizedHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
}
public void turnDownwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
} else if (normalizedHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
}
/**
* Takes the VMG from the polartable for upwind or downwind depending on the boats direction,
* and uses this to calculate a heading to move the yacht towards.
*/
public void turnToVMG() {
if (isAuto) {
disableAutoPilot();
} else {
Double normalizedHeading = normalizeHeading();
Double optimalHeading;
HashMap<Double, Double> optimalPolarMap;
if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind
optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots());
} else {
optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots());
}
optimalHeading = optimalPolarMap.keySet().iterator().next();
if (normalizedHeading > 180) {
optimalHeading = 360 - optimalHeading;
}
// Take optimal heading and turn into a boat heading rather than a wind heading.
optimalHeading =
optimalHeading + GameState.getWindDirection();
setAutoPilot(optimalHeading);
}
}
/**
* Takes a given heading and rotates the boat towards that heading. This does not care about
* being upwind or downwind, just which direction will reach a given heading faster.
*
* @param newHeading The heading to turn the yacht towards.
*/
private void turnTowardsHeading(Double newHeading) {
Double newVal = heading - newHeading;
if (Math.floorMod(newVal.longValue(), 360L) > 180) {
adjustHeading(TURN_STEP / 5);
} else {
adjustHeading(-TURN_STEP / 5);
}
}
/**
* Returns a heading normalized for the wind direction. Heading direction into the wind is 0,
* directly away is 180.
*
* @return The normalized heading accounting for wind direction.
*/
private Double normalizeHeading() {
Double normalizedHeading = heading - GameState.windDirection;
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
return normalizedHeading;
}
public Integer getSourceId() {
//@TODO Remove and merge with Creating Game Loop
if (sourceId == null) {
return 0;
}
return sourceId;
}
// TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE
public String getHullID() {
if (hullID == null) {
return "";
}
return hullID;
}
// TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE
public String getShortName() {
return shortName;
}
public String getBoatName() {
return boatName;
}
public String getCountry() {
if (country == null) {
return "";
}
return country;
}
public GeoPoint getLocation() {
return location;
}
public Double getHeading() {
return heading;
}
public void setHeading(Double heading) {
this.heading = heading;
}
public Boolean getSailIn() {
return sailIn;
}
@Override
public String toString() {
return boatName;
}
public Double getCurrentVelocity() {
return currentVelocity;
}
public void setCurrentVelocity(Double currentVelocity) {
this.currentVelocity = currentVelocity;
}
public Integer getCurrentMarkSeqID() {
return currentMarkSeqID;
}
public GeoPoint getLastLocation() {
return lastLocation;
}
public Mark getClosestCurrentMark() {
return closestCurrentMark;
}
public void setClosestCurrentMark(Mark closestCurrentMark) {
this.closestCurrentMark = closestCurrentMark;
}
public void setHasEnteredRoundingZone(Boolean hasEnteredRoundingZone) {
this.hasEnteredRoundingZone = hasEnteredRoundingZone;
}
public void setHasPassedLine(Boolean hasPassedLine) {
this.hasPassedLine = hasPassedLine;
}
public void setHasPassedThroughGate(Boolean hasPassedThroughGate) {
this.hasPassedThroughGate = hasPassedThroughGate;
}
public BoatStatus getBoatStatus() {
return boatStatus;
}
public void setBoatStatus(BoatStatus boatStatus) {
this.boatStatus = boatStatus;
}
public void incrementMarkSeqID() {
currentMarkSeqID++;
}
public Boolean hasEnteredRoundingZone() {
return hasEnteredRoundingZone;
}
public Boolean hasPassedThroughGate() {
return hasPassedThroughGate;
}
public Boolean hasPassedLine() {
return hasPassedLine;
}
}
-601
View File
@@ -1,601 +0,0 @@
package seng302.model;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.GameState;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
/**
* Yacht class for the racing boat.
*
* Class created to store more variables (eg. boat statuses) compared to the XMLParser boat class,
* also done outside Boat class because some old variables are not used anymore.
*/
public class Yacht {
@FunctionalInterface
public interface YachtLocationListener {
void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity);
}
private Logger logger = LoggerFactory.getLogger(Yacht.class);
private static final Double ROUNDING_DISTANCE = 50d; // TODO: 3/08/17 wmu16 - Look into this value further
//BOTH AFAIK
private String boatType;
private Integer sourceId;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
private String boatName;
private String country;
private Long estimateTimeAtFinish;
private Integer currentMarkSeqID = 0;
private Long markRoundTime;
private Double distanceToCurrentMark;
private Long timeTillNext;
private Double heading;
private Integer legNumber = 0;
//SERVER SIDE
private final Double TURN_STEP = 5.0;
private Double lastHeading;
private Boolean sailIn;
private GeoPoint location;
private Integer boatStatus;
private Double velocity;
//MARK ROUNDING INFO
private GeoPoint lastLocation; //For purposes of mark rounding calculations
private Boolean hasEnteredRoundingZone; //The distance that the boat must be from the mark to round
private Boolean hasPassedLine;
private Boolean hasPassedThroughGate;
private Boolean finishedRace;
//CLIENT SIDE
private List<YachtLocationListener> locationListeners = new ArrayList<>();
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
private CompoundMark lastMarkRounded;
private Integer positionInt = 0;
private Color colour;
public Yacht(String boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
this.sourceId = sourceId;
this.hullID = hullID;
this.shortName = shortName;
this.boatName = boatName;
this.country = country;
this.sailIn = false;
this.location = new GeoPoint(57.670341, 11.826856);
this.lastLocation = location;
this.heading = 120.0; //In degrees
this.velocity = 0d; //in mms-1
this.hasEnteredRoundingZone = false;
this.hasPassedLine = false;
this.hasPassedThroughGate = false;
this.finishedRace = false;
}
/**
* @param timeInterval since last update in milliseconds
*/
public void update(Long timeInterval) {
Double secondsElapsed = timeInterval / 1000000.0;
Double windSpeedKnots = GameState.getWindSpeedKnots();
Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading);
Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle);
Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000;
if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) {
if (velocity < maxBoatSpeed) {
velocity += maxBoatSpeed / 15; // Acceleration
}
if (velocity > maxBoatSpeed) {
velocity = maxBoatSpeed; // Prevent the boats from exceeding top speed
}
} else { // Deceleration
if (velocity > 0d) {
if (maxBoatSpeed != 0d) {
velocity -= maxBoatSpeed / 600;
} else {
velocity -= velocity / 100;
}
if (velocity < 0) {
velocity = 0d;
}
}
}
//UPDATE BOAT LOCATION
lastLocation = location;
location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed);
//CHECK FOR MARK ROUNDING
if (!finishedRace) {
checkForLegProgression();
}
// TODO: 3/08/17 wmu16 - Implement line cross check here
}
/**
* Calculates the distance to the next mark (closest of the two if a gate mark). For purposes
* of mark rounding
* @return A distance in metres. Returns -1 if there is no next mark
* @throws IndexOutOfBoundsException If the next mark is null (ie the last mark in the race)
* Check first using {@link seng302.model.mark.MarkOrder#isLastMark(Integer)}
*/
public Double calcDistanceToCurrentMark() throws IndexOutOfBoundsException {
CompoundMark nextMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID);
if (nextMark.isGate()) {
Mark sub1 = nextMark.getSubMark(1);
Mark sub2 = nextMark.getSubMark(2);
Double distance1 = GeoUtility.getDistance(location, sub1);
Double distance2 = GeoUtility.getDistance(location, sub2);
return (distance1 < distance2) ? distance1 : distance2;
} else {
return GeoUtility.getDistance(location, nextMark.getSubMark(1));
}
}
/**
* 4 Different cases of progression in the race
* 1 - Passing the start line
* 2 - Passing any in-race Gate
* 3 - Passing any in-race Mark
* 4 - Passing the finish line
*/
private void checkForLegProgression() {
CompoundMark currentMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID);
if (currentMarkSeqID == 0) {
checkStartLineCrossing(currentMark);
} else if (GameState.getMarkOrder().isLastMark(currentMarkSeqID)) {
checkFinishLineCrossing(currentMark);
} else if (currentMark.isGate()) {
checkGateRounding(currentMark);
} else {
checkMarkRounding(currentMark);
}
}
/**
* If we pass the start line gate in the correct direction, progress
*
* @param currentMark The current gate
*/
private void checkStartLineCrossing(CompoundMark currentMark) {
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
currentMarkSeqID++;
logMarkRounding(currentMark);
}
}
}
/**
* This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute
* of the yacht if so.
* A visual representation of this algorithm can be seen on the Wiki under
* 'mark passing algorithm'
*/
private void checkMarkRounding(CompoundMark currentMark) {
distanceToCurrentMark = calcDistanceToCurrentMark();
GeoPoint nextPoint = GameState.getMarkOrder().getNextMark(currentMarkSeqID).getMidPoint();
GeoPoint prevPoint = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID)
.getMidPoint();
GeoPoint midPoint = GeoUtility.getDirtyMidPoint(nextPoint, prevPoint);
//1 TEST FOR ENTERING THE ROUNDING DISTANCE
if (distanceToCurrentMark < ROUNDING_DISTANCE) {
hasEnteredRoundingZone = true;
}
//In case current mark is a gate, loop through all marks just in case
for (Mark thisCurrentMark : currentMark.getMarks()) {
if (GeoUtility.isPointInTriangle(lastLocation, location, midPoint, thisCurrentMark)) {
hasPassedLine = true;
}
}
if (hasPassedLine && hasEnteredRoundingZone) {
currentMarkSeqID++;
hasPassedLine = false;
hasEnteredRoundingZone = false;
hasPassedThroughGate = false;
logMarkRounding(currentMark);
}
}
/**
* Checks if a gate line has been crossed and in the correct direction
*
* @param currentMark The current gate
*/
private void checkGateRounding(CompoundMark currentMark) {
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID);
CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
//We have crossed the line
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
//Check we cross the line in the correct direction
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
hasPassedThroughGate = true;
}
}
Boolean prevMarkSide = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
Boolean nextMarkSide = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
if (hasPassedThroughGate) {
//Check if we need to round this gate after passing through
if (prevMarkSide == nextMarkSide) {
checkMarkRounding(currentMark);
} else {
currentMarkSeqID++;
logMarkRounding(currentMark);
}
}
}
/**
* If we pass the finish gate in the correct direction
*
* @param currentMark The current gate
*/
private void checkFinishLineCrossing(CompoundMark currentMark) {
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
currentMarkSeqID++;
finishedRace = true;
logMarkRounding(currentMark);
logger.debug(sourceId + " finished");
// TODO: 8/08/17 wmu16 - Do something!
}
}
}
public void adjustHeading(Double amount) {
Double newVal = heading + amount;
lastHeading = heading;
heading = (double) Math.floorMod(newVal.longValue(), 360L);
}
public void tackGybe(Double windDirection) {
Double normalizedHeading = normalizeHeading();
adjustHeading(-2 * normalizedHeading);
}
public void toggleSailIn() {
sailIn = !sailIn;
}
public void turnUpwind() {
Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
} else if (normalizedHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
}
public void turnDownwind() {
Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
} else if (normalizedHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
}
public void turnToVMG() {
Double normalizedHeading = normalizeHeading();
Double optimalHeading;
HashMap<Double, Double> optimalPolarMap;
if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind
optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots());
optimalHeading = optimalPolarMap.keySet().iterator().next();
} else {
optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots());
optimalHeading = optimalPolarMap.keySet().iterator().next();
}
// Take optimal heading and turn into correct
optimalHeading =
optimalHeading + (double) Math.floorMod(GameState.getWindDirection().longValue(), 360L);
turnTowardsHeading(optimalHeading);
}
private void turnTowardsHeading(Double newHeading) {
if (heading < 90 && newHeading > 270) {
adjustHeading(-TURN_STEP);
} else {
if (heading < newHeading) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
}
}
private Double normalizeHeading() {
Double normalizedHeading = heading - GameState.windDirection;
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
return normalizedHeading;
}
public String getBoatType() {
return boatType;
}
public Integer getSourceId() {
//@TODO Remove and merge with Creating Game Loop
if (sourceId == null) return 0;
return sourceId;
}
public String getHullID() {
if (hullID == null) return "";
return hullID;
}
public String getShortName() {
return shortName;
}
public String getBoatName() {
return boatName;
}
public String getCountry() {
if (country == null) return "";
return country;
}
public Integer getBoatStatus() {
return boatStatus;
}
public void setBoatStatus(Integer boatStatus) {
this.boatStatus = boatStatus;
}
public Integer getLegNumber() {
return legNumber;
}
public void setLegNumber(Integer legNumber) {
// if (colour != null && position != "-" && legNumber != this.legNumber) {
// RaceViewController.updateYachtPositionSparkline(this, legNumber);
// }
this.legNumber = legNumber;
}
public void setEstimateTimeTillNextMark(Long estimateTimeTillNextMark) {
timeTillNext = estimateTimeTillNextMark;
}
public String getEstimateTimeAtFinish() {
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
return format.format(estimateTimeAtFinish);
}
public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) {
this.estimateTimeAtFinish = estimateTimeAtFinish;
}
public Integer getPositionInteger() {
return positionInt;
}
public void setPositionInteger(Integer position) {
this.positionInt = position;
}
public void updateVelocityProperty(double velocity) {
this.velocityProperty.set(velocity);
}
public void setMarkRoundingTime(Long markRoundingTime) {
this.markRoundTime = markRoundingTime;
}
public ReadOnlyDoubleProperty getVelocityProperty() {
return velocityProperty.getReadOnlyProperty();
}
public double getVelocityMMS() {
return velocity;
}
public ReadOnlyLongProperty timeTillNextProperty() {
return timeTillNextProperty.getReadOnlyProperty();
}
public Double getVelocityKnots() {
return velocity / 1000 * 1.943844492; // TODO: 26/07/17 cir27 - remove magic number
}
public Long getTimeTillNext() {
return timeTillNext;
}
public Long getMarkRoundTime() {
return markRoundTime;
}
public CompoundMark getLastMarkRounded() {
return lastMarkRounded;
}
public void setLastMarkRounded(CompoundMark lastMarkRounded) {
this.lastMarkRounded = lastMarkRounded;
}
public GeoPoint getLocation() {
return location;
}
/**
* Sets the current location of the boat in lat and long whilst preserving the last location
*
* @param lat Latitude
* @param lng Longitude
*/
public void setLocation(Double lat, Double lng) {
lastLocation.setLat(location.getLat());
lastLocation.setLng(location.getLng());
location.setLat(lat);
location.setLng(lng);
}
public Double getHeading() {
return heading;
}
public void setHeading(Double heading) {
this.heading = heading;
}
public Boolean getSailIn() {
return sailIn;
}
@Override
public String toString() {
return boatName;
}
public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) {
this.timeSinceLastMarkProperty.set(timeSinceLastMark);
}
public ReadOnlyLongProperty timeSinceLastMarkProperty () {
return timeSinceLastMarkProperty.getReadOnlyProperty();
}
public void setTimeTillNext(Long timeTillNext) {
this.timeTillNext = timeTillNext;
}
public Color getColour() {
return colour;
}
public void setColour(Color colour) {
this.colour = colour;
}
public Double getVelocity() {
return velocity;
}
public void setVelocity(Double velocity) {
this.velocity = velocity;
}
public Double getDistanceToCurrentMark() {
return distanceToCurrentMark;
}
public void updateLocation(double lat, double lng, double heading, double velocity) {
setLocation(lat, lng);
this.heading = heading;
this.velocity = velocity;
updateVelocityProperty(velocity);
for (YachtLocationListener yll : locationListeners) {
yll.notifyLocation(this, lat, lng, heading, velocity);
}
}
private void logMarkRounding(CompoundMark currentMark) {
String typeString = "mark";
if (currentMark.isGate()) {
typeString = "gate";
}
logger.debug(
String.format("BoatID %d passed %s %s with id %d. Now on leg %d",
sourceId,
typeString,
currentMark.getMarks().get(0).getName(),
currentMark.getId(),
currentMarkSeqID));
}
public void addLocationListener (YachtLocationListener listener) {
locationListeners.add(listener);
}
}
@@ -2,6 +2,7 @@ package seng302.model.mark;
import java.util.ArrayList;
import java.util.List;
import seng302.gameServer.server.messages.RoundingSide;
import seng302.model.GeoPoint;
import seng302.utilities.GeoUtility;
@@ -9,13 +10,12 @@ public class CompoundMark {
private int compoundMarkId;
private String name;
private List<Mark> marks = new ArrayList<>();
private GeoPoint midPoint;
public CompoundMark(int markID, String name, List<Mark> marks) {
this.compoundMarkId = markID;
this.name = name;
this.name = name;
this.marks.addAll(marks);
if (marks.size() > 1) {
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
@@ -55,6 +55,27 @@ public class CompoundMark {
this.name = name;
}
public void setRoundingSide(RoundingSide roundingSide) {
switch (roundingSide) {
case SP:
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
getSubMark(2).setRoundingSide(RoundingSide.PORT);
break;
case PS:
getSubMark(1).setRoundingSide(RoundingSide.PORT);
getSubMark(2).setRoundingSide(RoundingSide.STARBOARD);
break;
case PORT:
getSubMark(1).setRoundingSide(RoundingSide.PORT);
break;
case STARBOARD:
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
break;
}
}
/**
* Returns the mark contained in the compound mark. Marks are numbered 1 to n;
* @param singleMarkId the id of the desired mark contained in this compound mark.
+12 -5
View File
@@ -2,6 +2,7 @@ package seng302.model.mark;
import java.util.ArrayList;
import java.util.List;
import seng302.gameServer.server.messages.RoundingSide;
import seng302.model.GeoPoint;
/**
@@ -19,11 +20,13 @@ public class Mark extends GeoPoint {
private String name;
private int sourceID;
private List<PositionListener> positionListeners = new ArrayList<>();
private RoundingSide roundingSide;
public Mark(String name, double lat, double lng, int sourceID) {
public Mark(String name, int seqID, double lat, double lng, int sourceID) {
super(lat, lng);
this.name = name;
this.sourceID = sourceID;
this.seqID = seqID;
}
/**
@@ -39,10 +42,6 @@ public class Mark extends GeoPoint {
return seqID;
}
public void setSeqID(int seqID) {
this.seqID = seqID;
}
public String getName() {
return name;
}
@@ -55,6 +54,14 @@ public class Mark extends GeoPoint {
return sourceID;
}
public RoundingSide getRoundingSide() {
return roundingSide;
}
public void setRoundingSide(RoundingSide roundingSide) {
this.roundingSide = roundingSide;
}
public void setSourceID(int sourceID) {
this.sourceID = sourceID;
}
+22 -18
View File
@@ -1,24 +1,21 @@
package seng302.model.mark;
import java.io.IOException;
import java.io.StringReader;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.gameServer.server.messages.RoundingSide;
import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* Class to hold the order of the marks in the race.
@@ -26,6 +23,7 @@ import java.util.Map;
public class MarkOrder {
private List<CompoundMark> raceMarkOrder;
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
private Set<Mark> allMarks;
public MarkOrder(){
loadRaceProperties();
@@ -35,7 +33,7 @@ public class MarkOrder {
* @return An ordered list of marks in the race
* OR null if the mark order could not be loaded
*/
public List<CompoundMark> getMarkOrder(){
public List<CompoundMark> getMarkOrder() {
if (raceMarkOrder == null){
logger.warn("Race order accessed but not instantiated");
return null;
@@ -55,10 +53,9 @@ public class MarkOrder {
/**
* @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark last passed
* @throws IndexOutOfBoundsException if there is no next mark.
* Check seqID != 0 first
* @throws IndexOutOfBoundsException if there is no next mark. Check seqID != 0 first
*/
public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException{
public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID - 1);
}
@@ -69,23 +66,28 @@ public class MarkOrder {
/**
* @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark following the mark that the boat is heading to
* @throws IndexOutOfBoundsException if there is no next mark.
* Check using {@link #isLastMark(Integer)}
* @throws IndexOutOfBoundsException if there is no next mark. Check using {@link
* #isLastMark(Integer)}
*/
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException{
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID + 1);
}
public Set<Mark> getAllMarks(){
return Collections.unmodifiableSet(allMarks);
}
/**
* Loads the race order from an XML string
* @param xml An AC35 RaceXML
* @return An ordered list of marks in the race
*/
private List<CompoundMark> loadRaceOrderFromXML(String xml){
private List<CompoundMark> loadRaceOrderFromXML(String xml) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc;
allMarks = new HashSet<>();
try {
db = dbf.newDocumentBuilder();
@@ -105,7 +107,9 @@ public class MarkOrder {
for (Corner corner : corners){
CompoundMark compoundMark = marks.get(corner.getCompoundMarkID());
compoundMark.setRoundingSide(RoundingSide.getRoundingSide(corner.getRounding()));
course.add(compoundMark);
allMarks.addAll(compoundMark.getMarks());
}
return course;
@@ -0,0 +1,34 @@
package seng302.model.stream.parser;
/**
* Stores parsed data from yacht event code packet
*/
public class YachtEventData {
private Long subjectId;
private Long incidentId;
private Integer eventId;
private Long timeStamp;
public YachtEventData(Long subjectId, Long incidentId, Integer eventId, Long timeStamp) {
this.subjectId = subjectId;
this.incidentId = incidentId;
this.eventId = eventId;
this.timeStamp = timeStamp;
}
public Long getSubjectId() {
return subjectId;
}
public Long getIncidentId() {
return incidentId;
}
public Integer getEventId() {
return eventId;
}
public Long getTimeStamp() {
return timeStamp;
}
}
@@ -4,13 +4,14 @@ import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import seng302.model.Yacht;
import seng302.model.ServerYacht;
/**
* A Race object that can be parsed into XML
*/
public class Race {
private List<Yacht> yachts;
private List<ServerYacht> yachts;
private LocalDateTime startTime;
public Race(){
@@ -22,7 +23,7 @@ public class Race {
* Add a boat to the race
* @param yacht The boat to add
*/
public void addBoat(Yacht yacht){
public void addBoat(ServerYacht yacht) {
yachts.add(yacht);
}
@@ -30,7 +31,7 @@ public class Race {
* Get a list of boats in the race
* @return A List of boats
*/
public List<Yacht> getBoats(){
public List<ServerYacht> getBoats() {
return Collections.unmodifiableList(yachts);
}