mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
Merge remote-tracking branch 'origin/develop' into 1124_broadcast_mark_rounding_message
# Conflicts: # src/main/java/seng302/model/Yacht.java # src/main/java/seng302/visualiser/GameView.java
This commit is contained in:
@@ -28,6 +28,7 @@ public class MainServerThread extends Observable implements Runnable, ClientConn
|
|||||||
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
|
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
|
||||||
|
|
||||||
public MainServerThread() {
|
public MainServerThread() {
|
||||||
|
new GameState("localhost");
|
||||||
try {
|
try {
|
||||||
serverSocket = new ServerSocket(PORT);
|
serverSocket = new ServerSocket(PORT);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -0,0 +1,683 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||||
|
import javafx.beans.property.ReadOnlyLongProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyLongWrapper;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.gameServer.GameState;
|
||||||
|
import seng302.model.mark.CompoundMark;
|
||||||
|
import seng302.model.mark.Mark;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yacht class for the racing boat.
|
||||||
|
*
|
||||||
|
* Class created to store more variables (eg. boat statuses) compared to the XMLParser boat class,
|
||||||
|
* also done outside Boat class because some old variables are not used anymore.
|
||||||
|
*/
|
||||||
|
public class Yacht {
|
||||||
|
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface YachtLocationListener {
|
||||||
|
void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity, boolean sailIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(Yacht.class);
|
||||||
|
|
||||||
|
private static final Double ROUNDING_DISTANCE = 50d; // TODO: 3/08/17 wmu16 - Look into this value further
|
||||||
|
|
||||||
|
|
||||||
|
//BOTH AFAIK
|
||||||
|
private String boatType;
|
||||||
|
private Integer sourceId;
|
||||||
|
private String hullID; //matches HullNum in the XML spec.
|
||||||
|
private String shortName;
|
||||||
|
private String boatName;
|
||||||
|
private String country;
|
||||||
|
|
||||||
|
private Long estimateTimeAtFinish;
|
||||||
|
private Integer currentMarkSeqID = 0;
|
||||||
|
private Long markRoundTime;
|
||||||
|
private Double distanceToCurrentMark;
|
||||||
|
private Long timeTillNext;
|
||||||
|
private Double heading;
|
||||||
|
private Integer legNumber = 0;
|
||||||
|
|
||||||
|
//SERVER SIDE
|
||||||
|
public static final Double TURN_STEP = 5.0; //This should be in some utils class somewhere 2bh. Public for tests sake.
|
||||||
|
private Double lastHeading;
|
||||||
|
private Boolean sailIn = false;
|
||||||
|
private GeoPoint location;
|
||||||
|
private Integer boatStatus;
|
||||||
|
private Double velocity;
|
||||||
|
private Boolean isAuto;
|
||||||
|
private Double autoHeading;
|
||||||
|
|
||||||
|
//MARK ROUNDING INFO
|
||||||
|
private GeoPoint lastLocation; //For purposes of mark rounding calculations
|
||||||
|
private Boolean hasEnteredRoundingZone; //The distance that the boat must be from the mark to round
|
||||||
|
private Boolean hasPassedLine;
|
||||||
|
private Boolean hasPassedThroughGate;
|
||||||
|
private Boolean finishedRace;
|
||||||
|
|
||||||
|
//CLIENT SIDE
|
||||||
|
private List<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;
|
||||||
|
private Boolean clientSailsIn = true;
|
||||||
|
|
||||||
|
public Yacht(String boatType, Integer sourceId, String hullID, String shortName,
|
||||||
|
String boatName, String country) {
|
||||||
|
this.boatType = boatType;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.hullID = hullID;
|
||||||
|
this.shortName = shortName;
|
||||||
|
this.boatName = boatName;
|
||||||
|
this.country = country;
|
||||||
|
this.sailIn = false;
|
||||||
|
this.isAuto = false;
|
||||||
|
this.location = new GeoPoint(57.670341, 11.826856);
|
||||||
|
this.lastLocation = location;
|
||||||
|
this.heading = 120.0; //In degrees
|
||||||
|
this.velocity = 0d; //in mms-1
|
||||||
|
|
||||||
|
this.hasEnteredRoundingZone = false;
|
||||||
|
this.hasPassedLine = false;
|
||||||
|
this.hasPassedThroughGate = false;
|
||||||
|
this.finishedRace = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param timeInterval since last update in milliseconds
|
||||||
|
*/
|
||||||
|
public void update(Long timeInterval) {
|
||||||
|
|
||||||
|
Double secondsElapsed = timeInterval / 1000000.0;
|
||||||
|
Double windSpeedKnots = GameState.getWindSpeedKnots();
|
||||||
|
Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading);
|
||||||
|
Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle);
|
||||||
|
Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000;
|
||||||
|
if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) {
|
||||||
|
|
||||||
|
if (velocity < maxBoatSpeed) {
|
||||||
|
velocity += maxBoatSpeed / 15; // Acceleration
|
||||||
|
}
|
||||||
|
if (velocity > maxBoatSpeed) {
|
||||||
|
velocity = maxBoatSpeed; // Prevent the boats from exceeding top speed
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // Deceleration
|
||||||
|
|
||||||
|
if (velocity > 0d) {
|
||||||
|
if (maxBoatSpeed != 0d) {
|
||||||
|
velocity -= maxBoatSpeed / 600;
|
||||||
|
} else {
|
||||||
|
velocity -= velocity / 100;
|
||||||
|
}
|
||||||
|
if (velocity < 0) {
|
||||||
|
velocity = 0d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runAutoPilot();
|
||||||
|
|
||||||
|
//UPDATE BOAT LOCATION
|
||||||
|
lastLocation = location;
|
||||||
|
location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed);
|
||||||
|
|
||||||
|
//CHECK FOR MARK ROUNDING
|
||||||
|
if (!finishedRace) {
|
||||||
|
checkForLegProgression();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 3/08/17 wmu16 - Implement line cross check here
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the distance to the next mark (closest of the two if a gate mark). For purposes
|
||||||
|
* of mark rounding
|
||||||
|
* @return A distance in metres. Returns -1 if there is no next mark
|
||||||
|
* @throws IndexOutOfBoundsException If the next mark is null (ie the last mark in the race)
|
||||||
|
* Check first using {@link seng302.model.mark.MarkOrder#isLastMark(Integer)}
|
||||||
|
*/
|
||||||
|
public Double calcDistanceToCurrentMark() throws IndexOutOfBoundsException {
|
||||||
|
CompoundMark nextMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID);
|
||||||
|
|
||||||
|
if (nextMark.isGate()) {
|
||||||
|
Mark sub1 = nextMark.getSubMark(1);
|
||||||
|
Mark sub2 = nextMark.getSubMark(2);
|
||||||
|
Double distance1 = GeoUtility.getDistance(location, sub1);
|
||||||
|
Double distance2 = GeoUtility.getDistance(location, sub2);
|
||||||
|
return (distance1 < distance2) ? distance1 : distance2;
|
||||||
|
} else {
|
||||||
|
return GeoUtility.getDistance(location, nextMark.getSubMark(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4 Different cases of progression in the race
|
||||||
|
* 1 - Passing the start line
|
||||||
|
* 2 - Passing any in-race Gate
|
||||||
|
* 3 - Passing any in-race Mark
|
||||||
|
* 4 - Passing the finish line
|
||||||
|
*/
|
||||||
|
private void checkForLegProgression() {
|
||||||
|
CompoundMark currentMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID);
|
||||||
|
if (currentMarkSeqID == 0) {
|
||||||
|
checkStartLineCrossing(currentMark);
|
||||||
|
} else if (GameState.getMarkOrder().isLastMark(currentMarkSeqID)) {
|
||||||
|
checkFinishLineCrossing(currentMark);
|
||||||
|
} else if (currentMark.isGate()) {
|
||||||
|
checkGateRounding(currentMark);
|
||||||
|
} else {
|
||||||
|
checkMarkRounding(currentMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we pass the start line gate in the correct direction, progress
|
||||||
|
*
|
||||||
|
* @param currentMark The current gate
|
||||||
|
*/
|
||||||
|
private void checkStartLineCrossing(CompoundMark currentMark) {
|
||||||
|
Mark mark1 = currentMark.getSubMark(1);
|
||||||
|
Mark mark2 = currentMark.getSubMark(2);
|
||||||
|
CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID);
|
||||||
|
|
||||||
|
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
|
||||||
|
if (crossedLine > 0) {
|
||||||
|
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
|
||||||
|
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
|
||||||
|
currentMarkSeqID++;
|
||||||
|
logMarkRounding(currentMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute
|
||||||
|
* of the yacht if so.
|
||||||
|
* A visual representation of this algorithm can be seen on the Wiki under
|
||||||
|
* 'mark passing algorithm'
|
||||||
|
*/
|
||||||
|
private void checkMarkRounding(CompoundMark currentMark) {
|
||||||
|
distanceToCurrentMark = calcDistanceToCurrentMark();
|
||||||
|
GeoPoint nextPoint = GameState.getMarkOrder().getNextMark(currentMarkSeqID).getMidPoint();
|
||||||
|
GeoPoint prevPoint = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID)
|
||||||
|
.getMidPoint();
|
||||||
|
GeoPoint midPoint = GeoUtility.getDirtyMidPoint(nextPoint, prevPoint);
|
||||||
|
|
||||||
|
//1 TEST FOR ENTERING THE ROUNDING DISTANCE
|
||||||
|
if (distanceToCurrentMark < ROUNDING_DISTANCE) {
|
||||||
|
hasEnteredRoundingZone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//In case current mark is a gate, loop through all marks just in case
|
||||||
|
for (Mark thisCurrentMark : currentMark.getMarks()) {
|
||||||
|
if (GeoUtility.isPointInTriangle(lastLocation, location, midPoint, thisCurrentMark)) {
|
||||||
|
hasPassedLine = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasPassedLine && hasEnteredRoundingZone) {
|
||||||
|
currentMarkSeqID++;
|
||||||
|
hasPassedLine = false;
|
||||||
|
hasEnteredRoundingZone = false;
|
||||||
|
hasPassedThroughGate = false;
|
||||||
|
logMarkRounding(currentMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a gate line has been crossed and in the correct direction
|
||||||
|
*
|
||||||
|
* @param currentMark The current gate
|
||||||
|
*/
|
||||||
|
private void checkGateRounding(CompoundMark currentMark) {
|
||||||
|
Mark mark1 = currentMark.getSubMark(1);
|
||||||
|
Mark mark2 = currentMark.getSubMark(2);
|
||||||
|
CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID);
|
||||||
|
CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID);
|
||||||
|
|
||||||
|
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
|
||||||
|
|
||||||
|
//We have crossed the line
|
||||||
|
if (crossedLine > 0) {
|
||||||
|
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
|
||||||
|
|
||||||
|
//Check we cross the line in the correct direction
|
||||||
|
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
|
||||||
|
hasPassedThroughGate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean prevMarkSide = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
|
||||||
|
Boolean nextMarkSide = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
|
||||||
|
|
||||||
|
if (hasPassedThroughGate) {
|
||||||
|
//Check if we need to round this gate after passing through
|
||||||
|
if (prevMarkSide == nextMarkSide) {
|
||||||
|
checkMarkRounding(currentMark);
|
||||||
|
} else {
|
||||||
|
currentMarkSeqID++;
|
||||||
|
logMarkRounding(currentMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we pass the finish gate in the correct direction
|
||||||
|
*
|
||||||
|
* @param currentMark The current gate
|
||||||
|
*/
|
||||||
|
private void checkFinishLineCrossing(CompoundMark currentMark) {
|
||||||
|
Mark mark1 = currentMark.getSubMark(1);
|
||||||
|
Mark mark2 = currentMark.getSubMark(2);
|
||||||
|
CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID);
|
||||||
|
|
||||||
|
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
|
||||||
|
if (crossedLine > 0) {
|
||||||
|
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
|
||||||
|
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
|
||||||
|
currentMarkSeqID++;
|
||||||
|
finishedRace = true;
|
||||||
|
logMarkRounding(currentMark);
|
||||||
|
logger.debug(sourceId + " finished");
|
||||||
|
// TODO: 8/08/17 wmu16 - Do something!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the heading of the boat by a given amount, while recording the boats
|
||||||
|
* last heading.
|
||||||
|
*
|
||||||
|
* @param amount the amount by which to adjust the boat heading.
|
||||||
|
*/
|
||||||
|
public void adjustHeading(Double amount) {
|
||||||
|
Double newVal = heading + amount;
|
||||||
|
lastHeading = heading;
|
||||||
|
heading = (double) Math.floorMod(newVal.longValue(), 360L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swaps the boats direction from one side of the wind to the other.
|
||||||
|
*/
|
||||||
|
public void tackGybe(Double windDirection) {
|
||||||
|
if (isAuto) {
|
||||||
|
disableAutoPilot();
|
||||||
|
} else {
|
||||||
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
Double newVal = (-2 * normalizedHeading) + heading;
|
||||||
|
Double newHeading = (double) Math.floorMod(newVal.longValue(), 360L);
|
||||||
|
setAutoPilot(newHeading);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the boats auto pilot feature, which will move the boat towards a given heading.
|
||||||
|
* @param thisHeading The heading to move the boat towards.
|
||||||
|
*/
|
||||||
|
private void setAutoPilot(Double thisHeading) {
|
||||||
|
isAuto = true;
|
||||||
|
autoHeading = thisHeading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the auto pilot function.
|
||||||
|
*/
|
||||||
|
public void disableAutoPilot() {
|
||||||
|
isAuto = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the boat towards the given heading when the auto pilot was set. Disables the auto pilot
|
||||||
|
* in the event that the boat is within the range of 1 turn step of its goal.
|
||||||
|
*/
|
||||||
|
public void runAutoPilot() {
|
||||||
|
if (isAuto) {
|
||||||
|
turnTowardsHeading(autoHeading);
|
||||||
|
if (Math.abs(heading - autoHeading)
|
||||||
|
<= TURN_STEP) { //Cancel when within 1 turn step of target.
|
||||||
|
isAuto = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleSailIn() {
|
||||||
|
sailIn = !sailIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void turnUpwind() {
|
||||||
|
disableAutoPilot();
|
||||||
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
if (normalizedHeading == 0) {
|
||||||
|
if (lastHeading < 180) {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
}
|
||||||
|
} else if (normalizedHeading == 180) {
|
||||||
|
if (lastHeading < 180) {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
}
|
||||||
|
} else if (normalizedHeading < 180) {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void turnDownwind() {
|
||||||
|
disableAutoPilot();
|
||||||
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
if (normalizedHeading == 0) {
|
||||||
|
if (lastHeading < 180) {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
}
|
||||||
|
} else if (normalizedHeading == 180) {
|
||||||
|
if (lastHeading < 180) {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
}
|
||||||
|
} else if (normalizedHeading < 180) {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the VMG from the polartable for upwind or downwind depending on the boats direction,
|
||||||
|
* and uses this to calculate a heading to move the yacht towards.
|
||||||
|
*/
|
||||||
|
public void turnToVMG() {
|
||||||
|
if (isAuto) {
|
||||||
|
disableAutoPilot();
|
||||||
|
} else {
|
||||||
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
Double optimalHeading;
|
||||||
|
HashMap<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 String getBoatType() {
|
||||||
|
return boatType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSourceId() {
|
||||||
|
//@TODO Remove and merge with Creating Game Loop
|
||||||
|
if (sourceId == null) return 0;
|
||||||
|
return sourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHullID() {
|
||||||
|
if (hullID == null) return "";
|
||||||
|
return hullID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShortName() {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBoatName() {
|
||||||
|
return boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCountry() {
|
||||||
|
if (country == null) return "";
|
||||||
|
return country;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getBoatStatus() {
|
||||||
|
return boatStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoatStatus(Integer boatStatus) {
|
||||||
|
this.boatStatus = boatStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLegNumber() {
|
||||||
|
return legNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLegNumber(Integer legNumber) {
|
||||||
|
// if (colour != null && position != "-" && legNumber != this.legNumber) {
|
||||||
|
// RaceViewController.updateYachtPositionSparkline(this, legNumber);
|
||||||
|
// }
|
||||||
|
this.legNumber = legNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEstimateTimeTillNextMark(Long estimateTimeTillNextMark) {
|
||||||
|
timeTillNext = estimateTimeTillNextMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEstimateTimeAtFinish() {
|
||||||
|
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||||
|
return format.format(estimateTimeAtFinish);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) {
|
||||||
|
this.estimateTimeAtFinish = estimateTimeAtFinish;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPositionInteger() {
|
||||||
|
return positionInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPositionInteger(Integer position) {
|
||||||
|
this.positionInt = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateVelocityProperty(double velocity) {
|
||||||
|
this.velocityProperty.set(velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMarkRoundingTime(Long markRoundingTime) {
|
||||||
|
this.markRoundTime = markRoundingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyDoubleProperty getVelocityProperty() {
|
||||||
|
return velocityProperty.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getVelocityMMS() {
|
||||||
|
return velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyLongProperty timeTillNextProperty() {
|
||||||
|
return timeTillNextProperty.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getVelocityKnots() {
|
||||||
|
return velocity / 1000 * 1.943844492; // TODO: 26/07/17 cir27 - remove magic number
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTimeTillNext() {
|
||||||
|
return timeTillNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getMarkRoundTime() {
|
||||||
|
return markRoundTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundMark getLastMarkRounded() {
|
||||||
|
return lastMarkRounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastMarkRounded(CompoundMark lastMarkRounded) {
|
||||||
|
this.lastMarkRounded = lastMarkRounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeoPoint getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current location of the boat in lat and long whilst preserving the last location
|
||||||
|
*
|
||||||
|
* @param lat Latitude
|
||||||
|
* @param lng Longitude
|
||||||
|
*/
|
||||||
|
public void setLocation(Double lat, Double lng) {
|
||||||
|
lastLocation.setLat(location.getLat());
|
||||||
|
lastLocation.setLng(location.getLng());
|
||||||
|
location.setLat(lat);
|
||||||
|
location.setLng(lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getHeading() {
|
||||||
|
return heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeading(Double heading) {
|
||||||
|
this.heading = heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getSailIn() {
|
||||||
|
return sailIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) {
|
||||||
|
this.timeSinceLastMarkProperty.set(timeSinceLastMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyLongProperty timeSinceLastMarkProperty () {
|
||||||
|
return timeSinceLastMarkProperty.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeTillNext(Long timeTillNext) {
|
||||||
|
this.timeTillNext = timeTillNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Color getColour() {
|
||||||
|
return colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColour(Color colour) {
|
||||||
|
this.colour = colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleClientSail() {
|
||||||
|
clientSailsIn = !clientSailsIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getVelocity() {
|
||||||
|
return velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVelocity(Double velocity) {
|
||||||
|
this.velocity = velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getDistanceToCurrentMark() {
|
||||||
|
return distanceToCurrentMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getClientSailsIn(){
|
||||||
|
return clientSailsIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateLocation(double lat, double lng, double heading, double velocity) {
|
||||||
|
setLocation(lat, lng);
|
||||||
|
this.heading = heading;
|
||||||
|
this.velocity = velocity;
|
||||||
|
updateVelocityProperty(velocity);
|
||||||
|
for (YachtLocationListener yll : locationListeners) {
|
||||||
|
yll.notifyLocation(this, lat, lng, heading, velocity, clientSailsIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logMarkRounding(CompoundMark currentMark) {
|
||||||
|
String typeString = "mark";
|
||||||
|
if (currentMark.isGate()) {
|
||||||
|
typeString = "gate";
|
||||||
|
}
|
||||||
|
logger.debug(
|
||||||
|
String.format("BoatID %d passed %s %s with id %d. Now on leg %d",
|
||||||
|
sourceId,
|
||||||
|
typeString,
|
||||||
|
currentMark.getMarks().get(0).getName(),
|
||||||
|
currentMark.getId(),
|
||||||
|
currentMarkSeqID));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addLocationListener (YachtLocationListener listener) {
|
||||||
|
locationListeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,8 @@ import seng302.model.stream.packets.StreamPacket;
|
|||||||
*/
|
*/
|
||||||
public class ClientToServerThread implements Runnable {
|
public class ClientToServerThread implements Runnable {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functional interface for receiving packets from client socket.
|
* Functional interface for receiving packets from client socket.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -257,6 +257,8 @@ public class GameClient {
|
|||||||
private void processRaceStatusUpdate(RaceStatusData data) {
|
private void processRaceStatusUpdate(RaceStatusData data) {
|
||||||
if (allXMLReceived()) {
|
if (allXMLReceived()) {
|
||||||
raceState.updateState(data);
|
raceState.updateState(data);
|
||||||
|
if (raceView != null)
|
||||||
|
raceView.getGameView().setWindDir(raceState.getWindDirection());
|
||||||
for (long[] boatData : data.getBoatData()) {
|
for (long[] boatData : data.getBoatData()) {
|
||||||
ClientYacht clientYacht = allBoatsMap.get((int) boatData[0]);
|
ClientYacht clientYacht = allBoatsMap.get((int) boatData[0]);
|
||||||
clientYacht.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]);
|
clientYacht.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]);
|
||||||
@@ -310,7 +312,9 @@ public class GameClient {
|
|||||||
switch (e.getCode()) {
|
switch (e.getCode()) {
|
||||||
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
|
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
|
||||||
case SHIFT: // sails in/sails out
|
case SHIFT: // sails in/sails out
|
||||||
socketThread.sendBoatAction(BoatAction.SAILS_IN); break;
|
socketThread.sendBoatAction(BoatAction.SAILS_IN);
|
||||||
|
raceView.getGameView().getPlayerYacht().toggleClientSail();
|
||||||
|
break;
|
||||||
case PAGE_UP:
|
case PAGE_UP:
|
||||||
case PAGE_DOWN:
|
case PAGE_DOWN:
|
||||||
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break;
|
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break;
|
||||||
|
|||||||
@@ -9,10 +9,14 @@ import java.util.Map;
|
|||||||
import javafx.animation.AnimationTimer;
|
import javafx.animation.AnimationTimer;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
import javafx.geometry.Point2D;
|
import javafx.geometry.Point2D;
|
||||||
import javafx.scene.Group;
|
import javafx.scene.Group;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.input.ScrollEvent;
|
||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
@@ -53,6 +57,8 @@ public class GameView extends Pane {
|
|||||||
private double referencePointX, referencePointY;
|
private double referencePointX, referencePointY;
|
||||||
private double metersPerPixelX, metersPerPixelY;
|
private double metersPerPixelX, metersPerPixelY;
|
||||||
|
|
||||||
|
final double SCALE_DELTA = 1.1;
|
||||||
|
|
||||||
private Text fpsDisplay = new Text();
|
private Text fpsDisplay = new Text();
|
||||||
private Polygon raceBorder = new CourseBoundary();
|
private Polygon raceBorder = new CourseBoundary();
|
||||||
|
|
||||||
@@ -80,6 +86,26 @@ public class GameView extends Pane {
|
|||||||
private Double frameRate = 60.0;
|
private Double frameRate = 60.0;
|
||||||
private int frameTimeIndex = 0;
|
private int frameTimeIndex = 0;
|
||||||
private boolean arrayFilled = false;
|
private boolean arrayFilled = false;
|
||||||
|
private ClientYacht playerYacht;
|
||||||
|
private double windDir = 0.0;
|
||||||
|
|
||||||
|
double scaleFactor = 1;
|
||||||
|
|
||||||
|
public void zoomOut() {
|
||||||
|
scaleFactor = 0.95;
|
||||||
|
for (Node child : getChildren()) {
|
||||||
|
child.setScaleX(child.getScaleX() * scaleFactor);
|
||||||
|
child.setScaleY(child.getScaleY() * scaleFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void zoomIn() {
|
||||||
|
scaleFactor = 1.05;
|
||||||
|
for (Node child : getChildren()) {
|
||||||
|
child.setScaleX(child.getScaleX() * scaleFactor);
|
||||||
|
child.setScaleY(child.getScaleY() * scaleFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private enum ScaleDirection {
|
private enum ScaleDirection {
|
||||||
HORIZONTAL,
|
HORIZONTAL,
|
||||||
@@ -96,6 +122,45 @@ public class GameView extends Pane {
|
|||||||
gameObjects.add(fpsDisplay);
|
gameObjects.add(fpsDisplay);
|
||||||
gameObjects.add(raceBorder);
|
gameObjects.add(raceBorder);
|
||||||
gameObjects.add(markers);
|
gameObjects.add(markers);
|
||||||
|
//
|
||||||
|
// this.setOnKeyPressed(new EventHandler<KeyEvent>() {
|
||||||
|
// @Override public void handle(KeyEvent event) {
|
||||||
|
// event.consume();
|
||||||
|
// switch (event.getCode()) {
|
||||||
|
// case Z:
|
||||||
|
// scaleFactor = scaleFactor * 1.2;
|
||||||
|
// break;
|
||||||
|
// case X:
|
||||||
|
// scaleFactor = scaleFactor * 0.8;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// if (event.getCode() == KeyCode.Z || event.getCode() == KeyCode.X) {
|
||||||
|
// for (Node child : getChildren()) {
|
||||||
|
// child.setScaleX(child.getScaleX() * scaleFactor);
|
||||||
|
// child.setScaleY(child.getScaleY() * scaleFactor);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// this.setOnScroll(new EventHandler<ScrollEvent>() {
|
||||||
|
// @Override public void handle(ScrollEvent event) {
|
||||||
|
// event.consume();
|
||||||
|
// if (event.getDeltaY() == 0) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// double scaleFactor =
|
||||||
|
// (event.getDeltaY() > 0)
|
||||||
|
// ? SCALE_DELTA
|
||||||
|
// : 1/SCALE_DELTA;
|
||||||
|
// for (Node child : getChildren()) {
|
||||||
|
// child.setScaleX(child.getScaleX() * scaleFactor);
|
||||||
|
// child.setScaleY(child.getScaleY() * scaleFactor);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
initializeTimer();
|
initializeTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,16 +266,6 @@ public class GameView extends Pane {
|
|||||||
for (Mark mark : cMark.getMarks()) {
|
for (Mark mark : cMark.getMarks()) {
|
||||||
makeAndBindMarker(mark, colour);
|
makeAndBindMarker(mark, colour);
|
||||||
}
|
}
|
||||||
|
|
||||||
//UNCOMMENT THIS TO HIGHLIGHT SUBMARKS 1 and 2 RED AND GREEN RESPECTIVELY FOR DEBUG
|
|
||||||
//(instead of above for loop)
|
|
||||||
// for (Mark mark : cMark.getMarks()) {
|
|
||||||
// if (mark.getSeqID() == 1) {
|
|
||||||
// makeAndBindMarker(mark, Color.RED);
|
|
||||||
// } else {
|
|
||||||
// makeAndBindMarker(mark, Color.GREEN);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//Create gate line
|
//Create gate line
|
||||||
if (cMark.isGate()) {
|
if (cMark.isGate()) {
|
||||||
for (int i = 1; i < cMark.getMarks().size(); i++) {
|
for (int i = 1; i < cMark.getMarks().size(); i++) {
|
||||||
@@ -334,10 +389,10 @@ public class GameView extends Pane {
|
|||||||
boatObjectGroup.getChildren().add(newBoat);
|
boatObjectGroup.getChildren().add(newBoat);
|
||||||
trails.getChildren().add(newBoat.getTrail());
|
trails.getChildren().add(newBoat.getTrail());
|
||||||
// TODO: 1/08/17 Make this less vile to look at.
|
// TODO: 1/08/17 Make this less vile to look at.
|
||||||
clientYacht.addLocationListener((boat, lat, lon, heading, velocity) -> {
|
clientYacht.addLocationListener((boat, lat, lon, heading, velocity, sailIn) -> {
|
||||||
BoatObject bo = boatObjects.get(boat);
|
BoatObject bo = boatObjects.get(boat);
|
||||||
Point2D p2d = findScaledXY(lat, lon);
|
Point2D p2d = findScaledXY(lat, lon);
|
||||||
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity);
|
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
|
||||||
// annotations.get(boat).setLayoutX(p2d.getX());
|
// annotations.get(boat).setLayoutX(p2d.getX());
|
||||||
// annotations.get(boat).setLayoutY(p2d.getY());
|
// annotations.get(boat).setLayoutY(p2d.getY());
|
||||||
// annotations.get(boat).setLocation(100d, 100d);
|
// annotations.get(boat).setLocation(100d, 100d);
|
||||||
@@ -572,11 +627,23 @@ public class GameView extends Pane {
|
|||||||
timer.stop();
|
timer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setWindDir(double windDir) {
|
||||||
|
this.windDir = windDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void startRace () {
|
public void startRace () {
|
||||||
timer.start();
|
timer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBoatAsPlayer(ClientYacht playerYacht) {
|
public ClientYacht getPlayerYacht() {
|
||||||
|
return playerYacht;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoatAsPlayer (ClientYacht playerYacht) {
|
||||||
|
this.playerYacht = playerYacht;
|
||||||
|
this.playerYacht.toggleClientSail();
|
||||||
boatObjects.get(playerYacht).setAsPlayer();
|
boatObjects.get(playerYacht).setAsPlayer();
|
||||||
annotations.get(playerYacht).addAnnotation(
|
annotations.get(playerYacht).addAnnotation(
|
||||||
"velocity",
|
"velocity",
|
||||||
|
|||||||
@@ -596,4 +596,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
this.courseData = raceData;
|
this.courseData = raceData;
|
||||||
gameView.updateBorder(raceData.getCourseLimit());
|
gameView.updateBorder(raceData.getCourseLimit());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GameView getGameView() {
|
||||||
|
return gameView;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ public class StartScreenController implements Initializable {
|
|||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
public void hostButtonPressed() {
|
public void hostButtonPressed() {
|
||||||
new GameState(getLocalHostIp());
|
// new GameState(getLocalHostIp());
|
||||||
gameClient = new GameClient(holder);
|
gameClient = new GameClient(holder);
|
||||||
gameClient.runAsHost(getLocalHostIp(), 4942);
|
gameClient.runAsHost(getLocalHostIp(), 4942);
|
||||||
// try {
|
// try {
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ public class BoatObject extends Group {
|
|||||||
private double xVelocity;
|
private double xVelocity;
|
||||||
private double yVelocity;
|
private double yVelocity;
|
||||||
private double lastHeading;
|
private double lastHeading;
|
||||||
|
private double sailState;
|
||||||
//Graphical objects
|
//Graphical objects
|
||||||
private Polyline trail = new Polyline();
|
private Polyline trail = new Polyline();
|
||||||
private Polygon boatPoly;
|
private Polygon boatPoly;
|
||||||
|
private Polygon sail;
|
||||||
private Wake wake;
|
private Wake wake;
|
||||||
private Line leftLayLine;
|
private Line leftLayLine;
|
||||||
private Line rightLayline;
|
private Line rightLayline;
|
||||||
@@ -94,7 +96,16 @@ public class BoatObject extends Group {
|
|||||||
trail.setCache(true);
|
trail.setCache(true);
|
||||||
wake = new Wake(0, -BOAT_HEIGHT);
|
wake = new Wake(0, -BOAT_HEIGHT);
|
||||||
wake.setVisible(true);
|
wake.setVisible(true);
|
||||||
super.getChildren().addAll(boatPoly);//, annotationBox);
|
|
||||||
|
sail = new Polygon(0.0,BOAT_HEIGHT / 4,
|
||||||
|
0.0, BOAT_HEIGHT);
|
||||||
|
sailState = 0;
|
||||||
|
sail.setStrokeWidth(2.0);
|
||||||
|
sail.setStroke(Color.BLACK);
|
||||||
|
sail.setFill(Color.TRANSPARENT);
|
||||||
|
sail.setCache(true);
|
||||||
|
super.getChildren().clear();
|
||||||
|
super.getChildren().addAll(boatPoly, sail);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFill (Paint value) {
|
public void setFill (Paint value) {
|
||||||
@@ -105,19 +116,30 @@ public class BoatObject extends Group {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves the boat and its children annotations to coordinates specified
|
* Moves the boat and its children annotations to coordinates specified
|
||||||
*
|
* @param x The X coordinate to move the boat to
|
||||||
* @param x The X coordinate to move the boat to
|
|
||||||
* @param y The Y coordinate to move the boat to
|
* @param y The Y coordinate to move the boat to
|
||||||
* @param rotation The rotation by which the boat moves
|
* @param rotation The rotation by which the boat moves
|
||||||
* @param velocity The velocity the boat is moving
|
* @param velocity The velocity the boat is moving
|
||||||
|
* @param sailIn
|
||||||
*/
|
*/
|
||||||
public void moveTo(double x, double y, double rotation, double velocity) {
|
public void moveTo(double x, double y, double rotation, double velocity, Boolean sailIn, double windDir) {
|
||||||
Double dx = Math.abs(boatPoly.getLayoutX() - x);
|
Double dx = Math.abs(boatPoly.getLayoutX() - x);
|
||||||
Double dy = Math.abs(boatPoly.getLayoutY() - y);
|
Double dy = Math.abs(boatPoly.getLayoutY() - y);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
rotateTo(rotation);
|
rotateTo(rotation, sailIn, windDir);
|
||||||
boatPoly.setLayoutX(x);
|
boatPoly.setLayoutX(x);
|
||||||
boatPoly.setLayoutY(y);
|
boatPoly.setLayoutY(y);
|
||||||
|
if (sailIn) {
|
||||||
|
// sail.getPoints().clear();
|
||||||
|
// sail.getPoints().addAll(0.0, 0.0, 4.0, 1.5, 8.0, 3.0, 12.0, 3.5, 16.0, 3.0, 20.0, 1.5, 24.0, 0.0);
|
||||||
|
// sail.getPoints().addAll(0.0, 0.0, 24.0, 0.0);
|
||||||
|
sail.setLayoutX(x);
|
||||||
|
sail.setLayoutY(y);
|
||||||
|
} else {
|
||||||
|
animateSail();
|
||||||
|
sail.setLayoutX(x);
|
||||||
|
sail.setLayoutY(y);
|
||||||
|
}
|
||||||
wake.setLayoutX(x);
|
wake.setLayoutX(x);
|
||||||
wake.setLayoutY(y);
|
wake.setLayoutY(y);
|
||||||
});
|
});
|
||||||
@@ -142,8 +164,65 @@ public class BoatObject extends Group {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rotateTo(double rotation) {
|
private Double normalizeHeading(double heading, double windDirection) {
|
||||||
boatPoly.getTransforms().setAll(new Rotate(rotation));
|
Double normalizedHeading = heading - windDirection;
|
||||||
|
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
|
||||||
|
return normalizedHeading;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void rotateTo(double heading, boolean sailsIn, double windDir) {
|
||||||
|
boatPoly.getTransforms().setAll(new Rotate(heading));
|
||||||
|
if (sailsIn) {
|
||||||
|
Double sailWindOffset = 30.0;
|
||||||
|
Double upwindAngleLimit = 15.0;
|
||||||
|
Double downwindAngleLimit = 10.0; //Upwind from normalised horizontal
|
||||||
|
Double normalizedHeading = normalizeHeading(heading, windDir);
|
||||||
|
if (normalizedHeading < 180) {
|
||||||
|
sail.getTransforms().setAll(new Rotate(windDir + 90 + sailWindOffset));
|
||||||
|
sail.getPoints().clear();
|
||||||
|
sail.getPoints().addAll(0.0, 0.0, 4.0, -1.5, 8.0, -3.0, 12.0, -3.5, 16.0, -3.0, 20.0, -1.5, 24.0, 0.0);
|
||||||
|
if (normalizedHeading > 90 + sailWindOffset){
|
||||||
|
sail.getTransforms().setAll(new Rotate(heading + downwindAngleLimit));
|
||||||
|
}
|
||||||
|
if (normalizedHeading < sailWindOffset + upwindAngleLimit){
|
||||||
|
sail.getTransforms().setAll(new Rotate(heading + 90 - upwindAngleLimit));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sail.getTransforms().setAll(new Rotate(windDir + 90 - sailWindOffset));
|
||||||
|
sail.getPoints().clear();
|
||||||
|
sail.getPoints().addAll(0.0, 0.0, 4.0, 1.5, 8.0, 3.0, 12.0, 3.5, 16.0, 3.0, 20.0, 1.5, 24.0, 0.0);
|
||||||
|
if (normalizedHeading < 270 - sailWindOffset){
|
||||||
|
sail.getTransforms().setAll(new Rotate(heading + 180 - downwindAngleLimit));
|
||||||
|
}
|
||||||
|
if (normalizedHeading > 360 - (sailWindOffset + upwindAngleLimit)){
|
||||||
|
sail.getTransforms().setAll(new Rotate(heading + 90 + upwindAngleLimit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sail.getTransforms().setAll(new Rotate(windDir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void animateSail(){
|
||||||
|
Double[] points = new Double[200];
|
||||||
|
double amplitude = 2.0;
|
||||||
|
double period = 10;
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
points[i * 2] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
|
||||||
|
points[i * 2 + 1] = (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
|
||||||
|
points[199 - (i * 2)] = (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
|
||||||
|
points[199 - (i * 2 + 1)] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
|
||||||
|
}
|
||||||
|
if (sailState == - 2 * Math.PI) {
|
||||||
|
sailState = 0;
|
||||||
|
} else {
|
||||||
|
sailState = sailState - Math.PI / 5;
|
||||||
|
}
|
||||||
|
sail.getPoints().clear();
|
||||||
|
sail.getPoints().addAll(points);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateLocation() {
|
public void updateLocation() {
|
||||||
@@ -275,11 +354,12 @@ public class BoatObject extends Group {
|
|||||||
boatPoly.setStroke(Color.BLACK);
|
boatPoly.setStroke(Color.BLACK);
|
||||||
boatPoly.setStrokeWidth(3);
|
boatPoly.setStrokeWidth(3);
|
||||||
isPlayer = true;
|
isPlayer = true;
|
||||||
|
animateSail();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTrajectory(double heading, double velocity) {
|
public void setTrajectory(double heading, double velocity, double windDir) {
|
||||||
wake.setRotation(lastHeading - heading, velocity);
|
wake.setRotation(lastHeading - heading, velocity);
|
||||||
rotateTo(heading);
|
rotateTo(heading, false, windDir);
|
||||||
xVelocity = Math.cos(Math.toRadians(heading)) * velocity;
|
xVelocity = Math.cos(Math.toRadians(heading)) * velocity;
|
||||||
yVelocity = Math.sin(Math.toRadians(heading)) * velocity;
|
yVelocity = Math.sin(Math.toRadians(heading)) * velocity;
|
||||||
lastHeading = heading;
|
lastHeading = heading;
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
<race-name>AC35</race-name>
|
<race-name>AC35</race-name>
|
||||||
<race-size>6</race-size>
|
<race-size>6</race-size>
|
||||||
<time-scale>10.0</time-scale>
|
<time-scale>10.0</time-scale>
|
||||||
<wind-direction>135</wind-direction>
|
<windDir-direction>135</windDir-direction>
|
||||||
</configurations>
|
</configurations>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import cucumber.api.CucumberOptions;
|
||||||
|
import cucumber.api.junit.Cucumber;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 7/08/17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@RunWith(Cucumber.class)
|
||||||
|
@CucumberOptions(features = "src/test/java/features")
|
||||||
|
public class RunCucumberTests {
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Feature: SailsToggle
|
||||||
|
Scenario: User toggles in sail
|
||||||
|
Given The game is running
|
||||||
|
When the user has pressed "shift"
|
||||||
|
Then the sails are "in"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package seng302.visualiser.map;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertFalse;
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.model.Yacht;
|
||||||
|
import seng302.visualiser.fxObjects.BoatObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 6/08/17.
|
||||||
|
*/
|
||||||
|
public class BoatSailAnimationToggleTest {
|
||||||
|
|
||||||
|
private Yacht yacht;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception{
|
||||||
|
yacht = new Yacht("Yacht", 1, "YACHT", "YAC", "Test Yacht", "NZ");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sailToggleTest() throws Exception {
|
||||||
|
assertFalse(yacht.getSailIn());
|
||||||
|
yacht.toggleClientSail();
|
||||||
|
assertFalse(yacht.getSailIn());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package steps;
|
||||||
|
|
||||||
|
import cucumber.api.java.en.Given;
|
||||||
|
import cucumber.api.java.en.Then;
|
||||||
|
import cucumber.api.java.en.When;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import seng302.gameServer.GameStages;
|
||||||
|
import seng302.gameServer.GameState;
|
||||||
|
import seng302.gameServer.MainServerThread;
|
||||||
|
import seng302.gameServer.server.messages.BoatAction;
|
||||||
|
import seng302.model.Yacht;
|
||||||
|
import seng302.visualiser.ClientToServerThread;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 7/08/17.
|
||||||
|
*/
|
||||||
|
public class ToggleSailSteps {
|
||||||
|
|
||||||
|
|
||||||
|
MainServerThread mst;
|
||||||
|
ClientToServerThread client;
|
||||||
|
boolean sailsIn = false;
|
||||||
|
long startTime;
|
||||||
|
private Yacht yacht;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Given("^The game is running$")
|
||||||
|
public void the_game_is_running() throws Throwable {
|
||||||
|
mst = new MainServerThread();
|
||||||
|
client = new ClientToServerThread("localhost", 4942);
|
||||||
|
GameState.setCurrentStage(GameStages.RACING);
|
||||||
|
Thread.sleep(200); // Sleep needed to help the threads all be up to speed with each other
|
||||||
|
Yacht yacht = (new ArrayList<>(GameState.getYachts().values())).get(0);
|
||||||
|
Assert.assertFalse(yacht.getSailIn());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@When("^the user has pressed \"([^\"]*)\"$")
|
||||||
|
public void the_user_has_pressed(String arg1) throws Throwable {
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
|
if (arg1 == "shift") {
|
||||||
|
client.sendBoatAction(BoatAction.SAILS_IN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Then("^the sails are \"([^\"]*)\"$")
|
||||||
|
public void the_sails_are(String arg1) throws Throwable {
|
||||||
|
Thread.sleep(200); // Sleep needed to help the threads all be up to speed with each other
|
||||||
|
Yacht yacht = (new ArrayList<>(GameState.getYachts().values())).get(0);
|
||||||
|
if (arg1 == "in") {
|
||||||
|
Assert.assertTrue(yacht.getSailIn());
|
||||||
|
} else {
|
||||||
|
Assert.assertFalse(yacht.getSailIn());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user