Merge branch 'develop' into story1118_map_arrows

This commit is contained in:
Calum
2017-08-16 01:35:15 +12:00
76 changed files with 3013 additions and 2460 deletions
+4
View File
@@ -180,3 +180,7 @@ local.properties
.recommenders/ .recommenders/
Makefile Makefile
infer-out/
infer.txt
log.log
+1 -1
View File
@@ -23,5 +23,5 @@ Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz> Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz>
Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz> Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz>
Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com> Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com>
Alistair McIntyre <ajm412@uclive.ac.nz> alistairjmcintyre <alistairjmcintyre@gmail.com> Alistair McIntyre <ajm412@uclive.ac.nz> <alistairjmcintyre@gmail.com>
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz> Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
+1 -1
View File
@@ -9,7 +9,7 @@ prints out event details, including time, involved boats and legs.
- Configuration file - Configuration file
We decided to store the team information including team names and boat velocity, as well as race configuration setting in external file. We decided to store the team information including team names and boat currentVelocity, as well as race configuration setting in external file.
To read external files, "Json-simple" library has been used to parse information. To read external files, "Json-simple" library has been used to parse information.
By using this library, we did not have to write our json parser and benefited from the flexibility of json files. By using this library, we did not have to write our json parser and benefited from the flexibility of json files.
+1 -1
View File
@@ -8,7 +8,7 @@ You can specify a config file using the using the -f flag, for example 'java -ja
## The config file ## The config file
The teams/boats are specified in the config file under 'teams', each team requires a team name, and a velocity (in meters per second). The teams/boats are specified in the config file under 'teams', each team requires a team name, and a currentVelocity (in meters per second).
The 'time-scale' option lets you change how long the race takes to complete. A time-scale of 1.0 is normal speed, 2.0 is 2x etc. The 'time-scale' option lets you change how long the race takes to complete. A time-scale of 1.0 is normal speed, 2.0 is 2x etc.
+1 -1
View File
@@ -69,8 +69,8 @@
<artifactId>commons-cli</artifactId> <artifactId>commons-cli</artifactId>
<version>1.4</version> <version>1.4</version>
</dependency> </dependency>
</dependencies>
</dependencies>
<build> <build>
<plugins> <plugins>
-2
View File
@@ -67,8 +67,6 @@ public class App extends Application {
@Override @Override
public void start(Stage primaryStage) throws Exception { public void start(Stage primaryStage) throws Exception {
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml")); Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml"));
primaryStage.setTitle("RaceVision"); primaryStage.setTitle("RaceVision");
Scene scene = new Scene(root, 1530, 960); Scene scene = new Scene(root, 1530, 960);
+438 -63
View File
@@ -1,20 +1,51 @@
package seng302.gameServer; package seng302.gameServer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import seng302.gameServer.server.messages.BoatActionType; import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.server.messages.BoatAction;
import seng302.gameServer.server.messages.BoatStatus;
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.YachtEventCodeMessage;
import seng302.model.GeoPoint;
import seng302.model.Player; import seng302.model.Player;
import seng302.model.Yacht; import seng302.model.PolarTable;
import seng302.model.ServerYacht;
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) * A Static class to hold information about the current state of the game (model)
* Also contains logic for updating itself on regular time intervals on its own thread
* Created by wmu16 on 10/07/17. * Created by wmu16 on 10/07/17.
*/ */
public class GameState implements Runnable { public class GameState implements Runnable {
private static Integer STATE_UPDATES_PER_SECOND = 60; @FunctionalInterface
interface NewMessageListener {
void notify(Message message);
}
private Logger logger = LoggerFactory.getLogger(GameState.class);
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
public static final Double MARK_COLLISION_DISTANCE = 15d;
public static final Double YACHT_COLLISION_DISTANCE = 25.0;
public static final Double BOUNCE_DISTANCE_MARK = 20.0;
public static final Double BOUNCE_DISTANCE_YACHT = 30.0;
public static final Double COLLISION_VELOCITY_PENALTY = 0.3;
private static Long previousUpdateTime; private static Long previousUpdateTime;
public static Double windDirection; public static Double windDirection;
@@ -22,10 +53,14 @@ public class GameState implements Runnable {
private static String hostIpAddress; private static String hostIpAddress;
private static List<Player> players; private static List<Player> players;
private static Map<Integer, Yacht> yachts; private static Map<Integer, ServerYacht> yachts;
private static Boolean isRaceStarted; private static Boolean isRaceStarted;
private static GameStages currentStage; private static GameStages currentStage;
private static MarkOrder markOrder;
private static long startTime; private static long startTime;
private static Set<Mark> marks;
private static List<NewMessageListener> markListeners;
private static Map<Player, String> playerStringMap = new HashMap<>(); private static Map<Player, String> playerStringMap = new HashMap<>();
/* /*
@@ -46,28 +81,35 @@ public class GameState implements Runnable {
yachts = new HashMap<>(); yachts = new HashMap<>();
players = new ArrayList<>(); players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress; GameState.hostIpAddress = hostIpAddress;
players = new ArrayList<>(); ;
currentStage = GameStages.LOBBYING; currentStage = GameStages.LOBBYING;
isRaceStarted = false; isRaceStarted = false;
yachts = new HashMap<>();
//set this when game stage changes to prerace //set this when game stage changes to prerace
previousUpdateTime = System.currentTimeMillis(); previousUpdateTime = System.currentTimeMillis();
yachts = new HashMap<>(); markOrder = new MarkOrder(); //This could be instantiated at some point with a select map?
markListeners = new ArrayList<>();
new Thread(this).start(); new Thread(this).start(); //Run the auto updates on the game state
marks = new MarkOrder().getAllMarks();
} }
public static String getHostIpAddress() { public static String getHostIpAddress() {
return hostIpAddress; return hostIpAddress;
} }
public static Set<Mark> getMarks(){
return Collections.unmodifiableSet(marks);
}
public static List<Player> getPlayers() { public static List<Player> getPlayers() {
return players; return players;
} }
public static void addPlayer(Player player) { public static void addPlayer(Player player) {
players.add(player); players.add(player);
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName() + " " + player.getYacht().getCountry(); String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName()
+ " " + player.getYacht().getCountry();
playerStringMap.put(player, playerText); playerStringMap.put(player, playerText);
} }
@@ -76,7 +118,7 @@ public class GameState implements Runnable {
playerStringMap.remove(player); playerStringMap.remove(player);
} }
public static void addYacht(Integer sourceId, Yacht yacht) { public static void addYacht(Integer sourceId, ServerYacht yacht) {
yachts.put(sourceId, yacht); yachts.put(sourceId, yacht);
} }
@@ -93,13 +135,17 @@ public class GameState implements Runnable {
} }
public static void setCurrentStage(GameStages currentStage) { public static void setCurrentStage(GameStages currentStage) {
if (currentStage == GameStages.RACING){ if (currentStage == GameStages.RACING) {
startTime = System.currentTimeMillis(); startTime = System.currentTimeMillis();
} }
GameState.currentStage = currentStage; GameState.currentStage = currentStage;
} }
public static MarkOrder getMarkOrder() {
return markOrder;
}
public static long getStartTime(){ public static long getStartTime(){
return startTime; return startTime;
} }
@@ -113,56 +159,17 @@ public class GameState implements Runnable {
} }
public static Double getWindSpeedKnots() { public static Double getWindSpeedKnots() {
return windSpeed / 1000 * 1.943844492; // TODO: 26/07/17 cir27 - remove magic numbers return GeoUtility.mmsToKnots(windSpeed); // TODO: 26/07/17 cir27 - remove magic numbers
} }
public static Map<Integer, Yacht> getYachts() { public static Map<Integer, ServerYacht> getYachts() {
return yachts; return yachts;
} }
public static void updateBoat(Integer sourceId, BoatActionType actionType) {
Yacht playerYacht = yachts.get(sourceId);
// System.out.println("-----------------------");
switch (actionType) {
case VMG:
playerYacht.turnToVMG();
// System.out.println("Snapping to VMG");
break;
case SAILS_IN:
playerYacht.toggleSailIn();
// System.out.println("Toggling Sails");
break;
case SAILS_OUT:
playerYacht.toggleSailIn();
// System.out.println("Toggling Sails");
break;
case TACK_GYBE:
playerYacht.tackGybe(windDirection);
// System.out.println("Tack/Gybe");
break;
case UPWIND:
playerYacht.turnUpwind();
// System.out.println("Moving upwind");
break;
case DOWNWIND:
playerYacht.turnDownwind();
// System.out.println("Moving downwind");
break;
}
// printBoatStatus(playerYacht);
}
public void update() {
Long timeInterval = System.currentTimeMillis() - previousUpdateTime;
previousUpdateTime = System.currentTimeMillis();
for (Yacht yacht : yachts.values()) {
yacht.update(timeInterval);
}
}
/** /**
* Generates a new ID based off the size of current players + 1 * Generates a new ID based off the size of current players + 1
*
* @return a playerID to be allocated to a new connetion * @return a playerID to be allocated to a new connetion
*/ */
public static Integer getUniquePlayerID() { public static Integer getUniquePlayerID() {
@@ -177,7 +184,7 @@ public class GameState implements Runnable {
@Override @Override
public void run() { public void run() {
while(true) { while (true) {
try { try {
Thread.sleep(1000 / STATE_UPDATES_PER_SECOND); Thread.sleep(1000 / STATE_UPDATES_PER_SECOND);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -187,20 +194,388 @@ public class GameState implements Runnable {
update(); update();
} }
//RACING
if (currentStage == GameStages.RACING) { if (currentStage == GameStages.RACING) {
update(); update();
} }
} }
} }
private static void printBoatStatus(Yacht playerYacht) { public static void updateBoat(Integer sourceId, BoatAction actionType) {
System.out.println("-----------------------"); ServerYacht playerYacht = yachts.get(sourceId);
System.out.println("Sails are in: " + playerYacht.getSailIn()); switch (actionType) {
System.out.println("Heading: " + playerYacht.getHeading()); case VMG:
System.out.println("Velocity: " + playerYacht.getVelocityMMS() / 1000); playerYacht.turnToVMG();
System.out.println("Lat: " + playerYacht.getLocation().getLat()); break;
System.out.println("Lng: " + playerYacht.getLocation().getLng()); case SAILS_IN:
System.out.println("-----------------------\n"); playerYacht.toggleSailIn();
break;
case SAILS_OUT:
playerYacht.toggleSailIn();
break;
case TACK_GYBE:
playerYacht.tackGybe(windDirection);
break;
case UPWIND:
playerYacht.turnUpwind();
break;
case DOWNWIND:
playerYacht.turnDownwind();
break;
}
}
/**
* Called periodically in this GameState thread to update the GameState values
*/
public void update() {
Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0;
previousUpdateTime = System.currentTimeMillis();
for (ServerYacht yacht : yachts.values()) {
updateVelocity(yacht);
yacht.runAutoPilot();
yacht.updateLocation(timeInterval);
if (!yacht.getFinishedRace()) {
checkForCollision(yacht);
checkForLegProgression(yacht);
}
}
}
public static void checkForCollision(ServerYacht serverYacht) {
ServerYacht collidedYacht = checkCollision(serverYacht);
if (collidedYacht != null) {
GeoPoint originalLocation = serverYacht.getLocation();
serverYacht.setLocation(
calculateBounceBack(serverYacht, originalLocation, BOUNCE_DISTANCE_YACHT)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
collidedYacht.setLocation(
calculateBounceBack(collidedYacht, originalLocation, BOUNCE_DISTANCE_YACHT)
);
collidedYacht.setCurrentVelocity(
collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId())
);
} else {
Mark collidedMark = markCollidedWith(serverYacht);
if (collidedMark != null) {
serverYacht.setLocation(
calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId())
);
}
}
}
private void updateVelocity(ServerYacht yacht) {
Double velocity = yacht.getCurrentVelocity();
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots);
// TODO: 15/08/17 remove magic numbers from these equations.
if (yacht.getSailIn()) {
if (velocity < maxBoatSpeed - 500) {
yacht.changeVelocity(maxBoatSpeed / 100);
} else if (velocity > maxBoatSpeed + 500) {
yacht.changeVelocity(-maxBoatSpeed / 100);
} else {
yacht.setCurrentVelocity(maxBoatSpeed);
}
} else {
if (velocity > 3000) {
yacht.changeVelocity(-velocity / 200);
} else if (velocity > 100) {
yacht.changeVelocity(-velocity / 50);
} else if (velocity <= 100){
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(ServerYacht 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(ServerYacht 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(ServerYacht 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);
yacht.setBoatStatus(BoatStatus.RACING);
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(ServerYacht 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(ServerYacht 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(ServerYacht 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);
yacht.setBoatStatus(BoatStatus.FINISHED);
return true;
}
}
return false;
}
private static Mark markCollidedWith(ServerYacht yacht) {
Set<Mark> marksInRace = GameState.getMarks();
for (Mark mark : marksInRace) {
if (GeoUtility.getDistance(yacht.getLocation(), mark)
<= MARK_COLLISION_DISTANCE) {
return mark;
}
}
return null;
}
/**
* Calculate the new position of the boat after it has had a collision
*
* @return The boats new position
*/
private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith, Double bounceDistance) {
Double heading = GeoUtility.getBearing(yacht.getLocation(), collidedWith);
// Invert heading
heading -= 180;
Integer newHeading = Math.floorMod(heading.intValue(), 360);
return GeoUtility.getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance);
}
/**
* Collision detection which iterates through all the yachts and check if any yacht collided
* with this yacht. Return collided yacht or null if no collision.
*
* @return yacht to compare to all other yachts.
*/
private static ServerYacht checkCollision(ServerYacht yacht) {
for (ServerYacht otherYacht : GameState.getYachts().values()) {
if (otherYacht != yacht) {
Double distance = GeoUtility.getDistance(otherYacht.getLocation(), yacht.getLocation());
if (distance < YACHT_COLLISION_DISTANCE) {
return otherYacht;
}
}
}
return null;
}
private void sendMarkRoundingMessage(ServerYacht 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());
notifyMessageListeners(markRoundingMessage);
}
private static void notifyMessageListeners(Message message) {
for (NewMessageListener mpl : markListeners) {
mpl.notify(message);
}
}
private void logMarkRounding(ServerYacht yacht) {
Mark roundingMark = yacht.getClosestCurrentMark();
logger.debug(
String.format("Yacht srcID(%d) passed Mark srcID(%d)", yacht.getSourceId(),
roundingMark.getSourceID()));
}
public static void addMarkPassListener(NewMessageListener listener) {
markListeners.add(listener);
} }
} }
@@ -42,7 +42,6 @@ public class HeartbeatThread extends Thread{
*/ */
private void sendHeartbeatToAllPlayers(){ private void sendHeartbeatToAllPlayers(){
Message heartbeat = new Heartbeat(seqNum); Message heartbeat = new Heartbeat(seqNum);
for (Player player : GameState.getPlayers()){ for (Player player : GameState.getPlayers()){
if (!player.getSocket().isConnected()) { if (!player.getSocket().isConnected()) {
playerLostConnection(player); playerLostConnection(player);
@@ -54,7 +53,6 @@ public class HeartbeatThread extends Thread{
playerLostConnection(player); playerLostConnection(player);
} }
} }
updateDelegate(); updateDelegate();
seqNum++; seqNum++;
} }
@@ -71,7 +69,6 @@ public class HeartbeatThread extends Thread{
public void run(){ public void run(){
Timer t = new Timer(); Timer t = new Timer();
t.schedule(new TimerTask() { t.schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
@@ -4,16 +4,22 @@ import java.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Observable;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import seng302.gameServer.server.messages.Message;
import seng302.model.GeoPoint;
import seng302.model.Player; import seng302.model.Player;
import seng302.model.PolarTable;
import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.utilities.GeoUtility;
import seng302.visualiser.GameClient;
/** /**
* A class describing the overall server, which creates and collects server threads for each client * A class describing the overall server, which creates and collects server threads for each client
* Created by wmu16 on 13/07/17. * Created by wmu16 on 13/07/17.
*/ */
public class MainServerThread extends Observable implements Runnable, ClientConnectionDelegate{ public class MainServerThread implements Runnable, ClientConnectionDelegate {
private static final int PORT = 4942; private static final int PORT = 4942;
private static final Integer CLIENT_UPDATES_PER_SECOND = 10; private static final Integer CLIENT_UPDATES_PER_SECOND = 10;
@@ -25,13 +31,17 @@ public class MainServerThread extends Observable implements Runnable, ClientConn
private ServerSocket serverSocket = null; private ServerSocket serverSocket = null;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>(); private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
private GameClient gameClient;
public MainServerThread() { public MainServerThread() {
new GameState("localhost");
try { try {
serverSocket = new ServerSocket(PORT); serverSocket = new ServerSocket(PORT);
} catch (IOException e) { } catch (IOException e) {
serverLog("IO error in server thread handler upon trying to make new server socket", 0); serverLog("IO error in server thread handler upon trying to make new server socket", 0);
} }
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
GameState.addMarkPassListener(this::broadcastMessage);
terminated = false; terminated = false;
thread = new Thread(this); thread = new Thread(this);
thread.start(); thread.start();
@@ -82,32 +92,43 @@ public class MainServerThread extends Observable implements Runnable, ClientConn
public void updateClients() { public void updateClients() {
for (ServerToClientThread serverToClientThread : serverToClientThreads) { for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.updateClient(); serverToClientThread.sendBoatLocationPackets();
}
}
private void broadcastMessage(Message message) {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.sendMessage(message);
} }
} }
static void serverLog(String message, int logLevel){ static void serverLog(String message, int logLevel) {
if(logLevel <= LOG_LEVEL){ if (logLevel <= LOG_LEVEL) {
System.out.println("[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message); System.out.println(
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
} }
} }
/** /**
* A client has tried to connect to the server * A client has tried to connect to the server
*
* @param serverToClientThread The player that connected * @param serverToClientThread The player that connected
*/ */
@Override @Override
public void clientConnected(ServerToClientThread serverToClientThread) { public void clientConnected(ServerToClientThread serverToClientThread) {
serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0); serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
serverToClientThreads.add(serverToClientThread); serverToClientThreads.add(serverToClientThread);
this.addObserver(serverToClientThread); serverToClientThread.addConnectionListener(() -> {
setChanged(); for (ServerToClientThread thread : serverToClientThreads) {
notifyObservers(); thread.sendSetupMessages();
}
});
} }
/** /**
* A player has left the game, remove the player from the GameState * A player has left the game, remove the player from the GameState
*
* @param player The player that left * @param player The player that left
*/ */
@Override @Override
@@ -120,16 +141,19 @@ public class MainServerThread extends Observable implements Runnable, ClientConn
serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0); serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
GameState.removeYacht(player.getYacht().getSourceId()); GameState.removeYacht(player.getYacht().getSourceId());
GameState.removePlayer(player); GameState.removePlayer(player);
ServerToClientThread closedConnection = null;
for (ServerToClientThread serverToClientThread : serverToClientThreads) { for (ServerToClientThread serverToClientThread : serverToClientThreads) {
if (serverToClientThread.getSocket() == player.getSocket()) { if (serverToClientThread.getSocket() == player.getSocket()) {
this.deleteObserver(serverToClientThread); closedConnection = serverToClientThread;
} else {
serverToClientThread.sendSetupMessages();
} }
} }
setChanged(); serverToClientThreads.remove(closedConnection);
notifyObservers();
} }
public void startGame() { public void startGame() {
initialiseBoatPositions();
Timer t = new Timer(); Timer t = new Timer();
t.schedule(new TimerTask() { t.schedule(new TimerTask() {
@@ -146,4 +170,56 @@ public class MainServerThread extends Observable implements Runnable, ClientConn
public void terminate() { public void terminate() {
terminated = true; terminated = true;
} }
/**
* Pass GameClient to main server thread so it can access the properties inside.
*
* @param gameClient gameClient
*/
public void setGameClient(GameClient gameClient) {
this.gameClient = gameClient;
}
/**
* Initialise boats to specific spaced out geopoints behind starting line.
*/
private void initialiseBoatPositions() {
// Getting the start line compound marks
CompoundMark cm = gameClient.getCourseData().getCompoundMarks().get(1);
GeoPoint startMark1 = new GeoPoint(cm.getMarks().get(0).getLat(),
cm.getMarks().get(0).getLng());
GeoPoint startMark2 = new GeoPoint(cm.getMarks().get(1).getLat(),
cm.getMarks().get(1).getLng());
// Calculating midpoint
Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
Double length = GeoUtility.getDistance(startMark1, startMark2);
GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
// Setting each boats position side by side
double DISTANCE_FACTOR = 50.0; // distance apart in meters
int boatIndex = 0;
for (ServerYacht yacht : GameState.getYachts().values()) {
int distanceApart = boatIndex / 2;
if (boatIndex % 2 == 1 && boatIndex != 0) {
distanceApart++;
distanceApart *= -1;
}
GeoPoint spawnMark = GeoUtility
.getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * DISTANCE_FACTOR);
if (yacht.getHeading() < perpendicularAngle) {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 90, DISTANCE_FACTOR);
} else {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 270, DISTANCE_FACTOR);
}
yacht.setLocation(spawnMark);
boatIndex++;
}
}
} }
@@ -1,37 +1,26 @@
package seng302.gameServer; package seng302.gameServer;
import java.util.Arrays; import java.util.Arrays;
import seng302.gameServer.server.messages.ClientType;
import seng302.gameServer.server.messages.Message;
import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.packets.StreamPacket;
import seng302.gameServer.server.messages.BoatActionType; import seng302.gameServer.server.messages.BoatAction;
public class ServerPacketParser { public class ServerPacketParser {
public static BoatAction extractBoatAction(StreamPacket packet) {
public static BoatActionType extractBoatAction(StreamPacket packet) {
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
long actionTypeValue = bytesToLong(Arrays.copyOfRange(payload, 0, 1)); long actionTypeValue = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
return BoatActionType.getType((int) actionTypeValue); return BoatAction.getType((int) actionTypeValue);
} }
/** public static ClientType extractClientType(StreamPacket packet){
* takes an array of up to 7 bytes and returns a positive byte[] payload = packet.getPayload();
* long constructed from the input bytes long value = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
* return ClientType.getClientType((int) value);
* @return a positive long if there is less than 7 bytes -1 otherwise
*/
private static long bytesToLong(byte[] bytes) {
long partialLong = 0;
int index = 0;
for (byte b : bytes) {
if (index > 6) {
return -1;
}
partialLong = partialLong | (b & 0xFFL) << (index * 8);
index++;
}
return partialLong;
} }
} }
@@ -9,7 +9,6 @@ import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Observable; import java.util.Observable;
@@ -18,23 +17,28 @@ import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import java.util.zip.Checksum; import java.util.zip.Checksum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.server.messages.YachtEventCodeMessage;
import seng302.model.Player; import seng302.model.Player;
import seng302.model.Yacht;
import seng302.model.stream.packets.PacketType; import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.xml.generator.Race; import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.generator.Regatta; import seng302.model.stream.xml.generator.Regatta;
import seng302.utilities.XMLGenerator; import seng302.utilities.XMLGenerator;
import seng302.gameServer.server.messages.BoatActionType; import seng302.gameServer.server.messages.BoatAction;
import seng302.gameServer.server.messages.BoatLocationMessage; import seng302.gameServer.server.messages.BoatLocationMessage;
import seng302.gameServer.server.messages.BoatStatus;
import seng302.gameServer.server.messages.BoatSubMessage; import seng302.gameServer.server.messages.BoatSubMessage;
import seng302.gameServer.server.messages.ClientType;
import seng302.gameServer.server.messages.Message; import seng302.gameServer.server.messages.Message;
import seng302.gameServer.server.messages.RaceStatus; import seng302.gameServer.server.messages.RaceStatus;
import seng302.gameServer.server.messages.RaceStatusMessage; import seng302.gameServer.server.messages.RaceStatusMessage;
import seng302.gameServer.server.messages.RaceType; import seng302.gameServer.server.messages.RaceType;
import seng302.gameServer.server.messages.RegistrationResponseMessage;
import seng302.gameServer.server.messages.RegistrationResponseStatus;
import seng302.gameServer.server.messages.XMLMessage; import seng302.gameServer.server.messages.XMLMessage;
import seng302.gameServer.server.messages.XMLMessageSubType; import seng302.gameServer.server.messages.XMLMessageSubType;
import seng302.model.ServerYacht;
/** /**
* A class describing a single connection to a Client for the purposes of sending and receiving on * A class describing a single connection to a Client for the purposes of sending and receiving on
@@ -43,8 +47,15 @@ import seng302.gameServer.server.messages.XMLMessageSubType;
*/ */
public class ServerToClientThread implements Runnable, Observer { public class ServerToClientThread implements Runnable, Observer {
private static final Integer LOG_LEVEL = 1; /**
private static final Integer MAX_ID_ATTEMPTS = 10; * Called to notify listeners when this thread receives a connection correctly.
*/
@FunctionalInterface
interface ConnectionListener {
void notifyConnection ();
}
private Logger logger = LoggerFactory.getLogger(ServerToClientThread.class);
private Thread thread; private Thread thread;
@@ -54,77 +65,103 @@ public class ServerToClientThread implements Runnable, Observer {
private ByteArrayOutputStream crcBuffer; private ByteArrayOutputStream crcBuffer;
private Boolean userIdentified = false;
private Boolean connected = true;
private Boolean updateClient = true;
// private Boolean initialisedRace = true;
private Integer seqNo; private Integer seqNo;
private Integer sourceId; private Integer sourceId;
private ClientType clientType;
private Boolean isRegistered = false;
private XMLGenerator xml; private XMLGenerator xml;
private List<ConnectionListener> connectionListeners = new ArrayList<>();
private ServerYacht yacht;
public ServerToClientThread(Socket socket) { public ServerToClientThread(Socket socket) {
this.socket = socket; this.socket = socket;
BufferedReader fn; seqNo = 0;
String fName = "";
BufferedReader ln; try{
String lName = "";
try {
is = socket.getInputStream(); is = socket.getInputStream();
os = socket.getOutputStream(); os = socket.getOutputStream();
fn = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_First_Names.csv"
)
)
);
List<String> all = fn.lines().collect(Collectors.toList());
fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
ln = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_Last_Names.csv"
)
)
);
all = ln.lines().collect(Collectors.toList());
lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
} catch (IOException e) { } catch (IOException e) {
serverLog("IO error in server thread upon grabbing streams", 1);
}
//Attempt threeway handshake with connection
sourceId = GameState.getUniquePlayerID();
if (threeWayHandshake(sourceId)) {
serverLog("Successful handshake. Client allocated id: " + sourceId, 0);
Yacht yacht = new Yacht(
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
);
// Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0);
GameState.addYacht(sourceId, yacht);
GameState.addPlayer(new Player(socket, yacht));
} else {
serverLog("Unsuccessful handshake. Connection rejected", 1);
closeSocket();
return; return;
} }
seqNo = 0;
thread = new Thread(this); thread = new Thread(this);
thread.start(); thread.start();
} }
static void serverLog(String message, int logLevel) { private void setUpPlayer(){
if (logLevel <= LOG_LEVEL) { BufferedReader fn;
System.out.println( String fName = "";
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message); BufferedReader ln;
} String lName = "";
fn = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_First_Names.csv"
)
)
);
List<String> all = fn.lines().collect(Collectors.toList());
fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
ln = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_Last_Names.csv"
)
)
);
all = ln.lines().collect(Collectors.toList());
lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
ServerYacht yacht = new ServerYacht(
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
);
yacht.addObserver(this); // TODO: yacht can notify mark rounding message hyi25 13/8/17
GameState.addYacht(sourceId, yacht);
GameState.addPlayer(new Player(socket, yacht));
} }
@Override @Override
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
sendSetupMessages(); if (arg != null) {
sendMessage((Message) arg);
} else {
sendSetupMessages();
}
}
private void completeRegistration(ClientType clientType) throws IOException {
// Fail if not a player
if (!clientType.equals(ClientType.PLAYER)){
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_GENERAL);
os.write(responseMessage.getBuffer());
return;
}
if (GameState.getPlayers().size() >= GameState.MAX_PLAYERS){
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL);
os.write(responseMessage.getBuffer());
return;
}
Integer sourceId = GameState.getUniquePlayerID();
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(sourceId, RegistrationResponseStatus.SUCCESS_PLAYING);
this.clientType = clientType;
this.sourceId = sourceId;
isRegistered = true;
os.write(responseMessage.getBuffer());
setUpPlayer();
for (ConnectionListener listener : connectionListeners) {
listener.notifyConnection();
}
} }
public void run() { public void run() {
@@ -135,20 +172,6 @@ public class ServerToClientThread implements Runnable, Observer {
while (socket.isConnected()) { while (socket.isConnected()) {
try { try {
//Perform a write if it is time to as delegated by the MainServerThread
if (updateClient) {
// TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream
// ChatterMessage chatterMessage = new ChatterMessage(4, 14, "Hello, it's me");
// sendMessage(chatterMessage);
// try {
// GameState.outputState(os);
// } catch (IOException e) {
// System.out.println("IO error in server thread upon writing to output stream");
// }
// sendBoatLocationPackets();
updateClient = false;
}
crcBuffer = new ByteArrayOutputStream(); crcBuffer = new ByteArrayOutputStream();
sync1 = readByte(); sync1 = readByte();
sync2 = readByte(); sync2 = readByte();
@@ -168,14 +191,21 @@ public class ServerToClientThread implements Runnable, Observer {
//System.out.println("RECEIVED A PACKET"); //System.out.println("RECEIVED A PACKET");
switch (PacketType.assignPacketType(type, payload)) { switch (PacketType.assignPacketType(type, payload)) {
case BOAT_ACTION: case BOAT_ACTION:
BoatActionType actionType = ServerPacketParser BoatAction actionType = ServerPacketParser
.extractBoatAction( .extractBoatAction(
new StreamPacket(type, payloadLength, timeStamp, payload)); new StreamPacket(type, payloadLength, timeStamp, payload));
GameState.updateBoat(sourceId, actionType); GameState.updateBoat(sourceId, actionType);
break; break;
case RACE_REGISTRATION_REQUEST:
ClientType requestedType = ServerPacketParser.extractClientType(
new StreamPacket(type, payloadLength, timeStamp, payload));
completeRegistration(requestedType);
break;
} }
} else { } else {
serverLog("Packet has been dropped", 1); logger.warn("Packet has been dropped", 1);
} }
} }
} catch (Exception e) { } catch (Exception e) {
@@ -187,11 +217,11 @@ public class ServerToClientThread implements Runnable, Observer {
} }
} }
private void sendSetupMessages() { public void sendSetupMessages() {
xml = new XMLGenerator(); xml = new XMLGenerator();
Race race = new Race(); Race race = new Race();
for (Yacht yacht : GameState.getYachts().values()) { for (ServerYacht yacht : GameState.getYachts().values()) {
race.addBoat(yacht); race.addBoat(yacht);
} }
@@ -213,44 +243,6 @@ public class ServerToClientThread implements Runnable, Observer {
sendMessage(xmlMessage); sendMessage(xmlMessage);
} }
public void updateClient() {
sendBoatLocationPackets();
updateClient = true;
}
/**
* Tries to confirm the connection just accepted.
* Sends ID, expects that ID echoed for confirmation,
* if so, sends a confirmation packet back to that connection
* Creates a player instance with that ID and this thread and adds it to the GameState
* If not, close the socket and end the threads execution
*
* @param id the id to try and assign to the connection
* @return A boolean indicating if it was a successful handshake
*/
private Boolean threeWayHandshake(Integer id) {
Integer confirmationID = null;
Integer identificationAttempt = 0;
while (!userIdentified) {
try {
os.write(id); //Send out new ID looking for echo
confirmationID = is.read();
} catch (IOException e) {
serverLog("Three way handshake failed", 1);
}
if (id.equals(confirmationID)) { //ID is echoed back. Connection is a client
return true;
} else if (identificationAttempt > MAX_ID_ATTEMPTS) { //No response. not a client. tidy up and go home.
return false;
}
identificationAttempt++;
}
return true;
}
private void closeSocket() { private void closeSocket() {
try { try {
socket.close(); socket.close();
@@ -259,7 +251,6 @@ public class ServerToClientThread implements Runnable, Observer {
} }
} }
private int readByte() throws Exception { private int readByte() throws Exception {
int currentByte = -1; int currentByte = -1;
try { try {
@@ -268,7 +259,7 @@ public class ServerToClientThread implements Runnable, Observer {
crcBuffer.write(currentByte); crcBuffer.write(currentByte);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
serverLog("Socket read failed", 1); logger.warn("Socket read failed", 1);
} }
if (currentByte == -1) { if (currentByte == -1) {
throw new Exception(); throw new Exception();
@@ -297,7 +288,7 @@ public class ServerToClientThread implements Runnable, Observer {
//serverLog("Player " + sourceId + " side socket disconnected", 1); //serverLog("Player " + sourceId + " side socket disconnected", 1);
return; return;
} catch (IOException e) { } catch (IOException e) {
serverLog("Message send failed", 1); logger.warn("Message send failed", 1);
} }
} }
@@ -307,10 +298,9 @@ public class ServerToClientThread implements Runnable, Observer {
} }
private void sendBoatLocationPackets() { public void sendBoatLocationPackets() {
ArrayList<Yacht> yachts = new ArrayList<>(GameState.getYachts().values()); ArrayList<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
for (Yacht yacht : yachts) { for (ServerYacht yacht : yachts) {
// System.out.println("[SERVER] Lat: " + yacht.getLocation().getLat() + " Lon: " + yacht.getLocation().getLng());
BoatLocationMessage boatLocationMessage = BoatLocationMessage boatLocationMessage =
new BoatLocationMessage( new BoatLocationMessage(
yacht.getSourceId(), yacht.getSourceId(),
@@ -318,7 +308,7 @@ public class ServerToClientThread implements Runnable, Observer {
yacht.getLocation().getLat(), yacht.getLocation().getLat(),
yacht.getLocation().getLng(), yacht.getLocation().getLng(),
yacht.getHeading(), yacht.getHeading(),
yacht.getVelocity().longValue()); yacht.getCurrentVelocity().longValue());
sendMessage(boatLocationMessage); sendMessage(boatLocationMessage);
} }
@@ -331,24 +321,14 @@ public class ServerToClientThread implements Runnable, Observer {
public void sendRaceStatusMessage() { public void sendRaceStatusMessage() {
// variables taken from GameServerThread // variables taken from GameServerThread
List<BoatSubMessage> boatSubMessages = new ArrayList<>(); List<BoatSubMessage> boatSubMessages = new ArrayList<>();
BoatStatus boatStatus;
RaceStatus raceStatus; RaceStatus raceStatus;
for (Player player : GameState.getPlayers()) { for (Player player : GameState.getPlayers()) {
Yacht y = player.getYacht(); ServerYacht y = player.getYacht();
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(), 0,
if (GameState.getCurrentStage() == GameStages.PRE_RACE) { 0, 0, 1234L,
boatStatus = BoatStatus.PRESTART; 1234L);
} else if (GameState.getCurrentStage() == GameStages.RACING) {
boatStatus = BoatStatus.RACING;
} else {
boatStatus = BoatStatus.UNDEFINED;
}
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), boatStatus, 0, 0, 0, 1234l,
1234l);
boatSubMessages.add(m); boatSubMessages.add(m);
} }
@@ -366,4 +346,20 @@ public class ServerToClientThread implements Runnable, Observer {
public Socket getSocket() { public Socket getSocket() {
return socket; return socket;
} }
public ServerYacht getYacht() {
return yacht;
}
public void sendCollisionMessage(Integer yachtId) {
sendMessage(new YachtEventCodeMessage(yachtId));
}
public void addConnectionListener(ConnectionListener listener) {
connectionListeners.add(listener);
}
public void removeConnectionListener(ConnectionListener listener) {
connectionListeners.remove(listener);
}
} }
@@ -6,29 +6,30 @@ import java.util.Map;
/** /**
* Created by kre39 on 12/07/17. * Created by kre39 on 12/07/17.
*/ */
public enum BoatActionType { public enum BoatAction {
VMG(1), VMG(1),
SAILS_IN(2), SAILS_IN(2),
SAILS_OUT(3), SAILS_OUT(3),
TACK_GYBE(4), TACK_GYBE(4),
UPWIND(5), UPWIND(5),
DOWNWIND(6); DOWNWIND(6),
MAINTAIN_HEADING(7);
private final int type; private final int type;
private static final Map<Integer, BoatActionType> intToTypeMap = new HashMap<>(); private static final Map<Integer, BoatAction> intToTypeMap = new HashMap<>();
static { static {
for (BoatActionType type : BoatActionType.values()) { for (BoatAction type : BoatAction.values()) {
intToTypeMap.put(type.getValue(), type); intToTypeMap.put(type.getValue(), type);
} }
} }
BoatActionType(int type){ BoatAction(int type){
this.type = type; this.type = type;
} }
public static BoatActionType getType(int value) { public static BoatAction getType(int value) {
return intToTypeMap.get(value); return intToTypeMap.get(value);
} }
@@ -6,9 +6,9 @@ package seng302.gameServer.server.messages;
public class BoatActionMessage extends Message{ public class BoatActionMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION; private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION;
private final int MESSAGE_SIZE = 1; private final int MESSAGE_SIZE = 1;
private BoatActionType actionType; private BoatAction actionType;
public BoatActionMessage(BoatActionType actionType) { public BoatActionMessage(BoatAction actionType) {
this.actionType = actionType; this.actionType = actionType;
setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id
allocateBuffer(); allocateBuffer();
@@ -0,0 +1,33 @@
package seng302.gameServer.server.messages;
public enum ClientType {
SPECTATOR(0x00),
PLAYER(0x01),
CONTROL_TUTORIAL(0x02),
GHOST_MODE(0x03);
private int type;
ClientType(int type){
this.type = type;
}
public int getCode(){
return type;
}
public static ClientType getClientType(int typeCode){
switch (typeCode){
case 0x00:
return SPECTATOR;
case 0x01:
return PLAYER;
case 0x02:
return CONTROL_TUTORIAL;
case 0x03:
return GHOST_MODE;
default:
return PLAYER;
}
}
}
@@ -1,5 +1,7 @@
package seng302.gameServer.server.messages; package seng302.gameServer.server.messages;
import seng302.gameServer.GameState;
public class MarkRoundingMessage extends Message{ public class MarkRoundingMessage extends Message{
private final long MESSAGE_VERSION_NUMBER = 1; private final long MESSAGE_VERSION_NUMBER = 1;
private final int MESSAGE_SIZE = 21; private final int MESSAGE_SIZE = 21;
@@ -18,14 +20,14 @@ public class MarkRoundingMessage extends Message{
* The purpose of this is to record the time when yachts cross marks * The purpose of this is to record the time when yachts cross marks
* @param ackNumber ackNumber * @param ackNumber ackNumber
* @param raceId raceId * @param raceId raceId
* @param sourceId sourceId * @param sourceId boatSourceId
* @param roundingBoatStatus roundingBoatStatus * @param roundingBoatStatus roundingBoatStatus
* @param roundingSide roundingSide * @param roundingSide roundingSide
* @param markId markId * @param markId markId
*/ */
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus, public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
RoundingSide roundingSide, int markId){ RoundingSide roundingSide, MarkType markType, int markId) {
this.time = System.currentTimeMillis() / 1000L; this.time = System.currentTimeMillis();
this.ackNumber = ackNumber; this.ackNumber = ackNumber;
this.raceId = raceId; this.raceId = raceId;
this.sourceId = sourceId; this.sourceId = sourceId;
@@ -44,6 +46,7 @@ public class MarkRoundingMessage extends Message{
putInt((int) sourceId, 4); putInt((int) sourceId, 4);
putByte((byte) boatStatus.getCode()); putByte((byte) boatStatus.getCode());
putByte((byte) roundingSide.getCode()); putByte((byte) roundingSide.getCode());
putByte((byte) markType.getCode());
putByte((byte) markId); putByte((byte) markId);
writeCRC(); writeCRC();
@@ -17,7 +17,9 @@ public enum MessageType {
MARK_ROUNDING(38), MARK_ROUNDING(38),
COURSE_WIND(44), COURSE_WIND(44),
AVERAGE_WIND(47), AVERAGE_WIND(47),
BOAT_ACTION(100); BOAT_ACTION(100),
REGISTRATION_REQUEST(101),
REGISTRATION_RESPONSE(102);
private int code; private int code;
@@ -32,4 +34,6 @@ public enum MessageType {
int getCode(){ int getCode(){
return this.code; return this.code;
} }
} }
@@ -0,0 +1,22 @@
package seng302.gameServer.server.messages;
public class RegistrationRequestMessage extends Message {
private static int MESSAGE_LENGTH = 2;
public RegistrationRequestMessage(ClientType type){
setHeader(new Header(MessageType.REGISTRATION_REQUEST, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putInt(type.getCode(), 2);
writeCRC();
}
@Override
public int getSize() {
return MESSAGE_LENGTH;
}
}
@@ -0,0 +1,20 @@
package seng302.gameServer.server.messages;
public class RegistrationResponseMessage extends Message{
public RegistrationResponseMessage(int clientSourceID, RegistrationResponseStatus status){
setHeader(new Header(MessageType.REGISTRATION_RESPONSE, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putInt(clientSourceID, 4);
putInt(status.getCode(), 1);
writeCRC();
}
@Override
public int getSize() {
return 5;
}
}
@@ -0,0 +1,44 @@
package seng302.gameServer.server.messages;
public enum RegistrationResponseStatus {
SUCCESS_SPECTATING(0x00),
SUCCESS_PLAYING(0x01),
SUCCESS_TUTORIAL(0x02),
SUCCESS_GHOSTING(0x03),
FAILURE_GENERAL(0x10),
FAILURE_FULL(0x11);
private int code;
RegistrationResponseStatus(int code){
this.code = code;
}
/**
* Get the message code (From the API Spec)
* @return the message code
*/
int getCode(){
return this.code;
}
public static RegistrationResponseStatus getResponseStatus(int typeCode){
switch (typeCode){
case 0x00:
return SUCCESS_SPECTATING;
case 0x01:
return SUCCESS_PLAYING;
case 0x02:
return SUCCESS_TUTORIAL;
case 0x03:
return SUCCESS_GHOSTING;
case 0x10:
return FAILURE_GENERAL;
case 0x11:
return FAILURE_FULL;
default:
return FAILURE_GENERAL;
}
}
}
@@ -4,17 +4,49 @@ package seng302.gameServer.server.messages;
* The side the boat rounded the mark * The side the boat rounded the mark
*/ */
public enum RoundingSide { public enum RoundingSide {
UNKNOWN(0), UNKNOWN(0, "Unknown"),
PORT(1), PORT(1, "Port"),
STARBOARD(2); STARBOARD(2, "Stbd"),
SP(3, "SP"),
PS(4, "PS");
private long code; private long code;
private String name;
RoundingSide(long code) { RoundingSide(long code, String name) {
this.code = code; this.code = code;
this.name = name;
} }
public long getCode(){ public long getCode(){
return code; return code;
} }
public String getName() {
return name;
}
public static RoundingSide getRoundingSide(String identifier) {
RoundingSide roundingSide = UNKNOWN;
switch (identifier) {
case "Unknown":
roundingSide = UNKNOWN;
break;
case "Port":
roundingSide = PORT;
break;
case "Stbd":
roundingSide = STARBOARD;
break;
case "SP":
roundingSide = SP;
break;
case "PS":
roundingSide = PS;
break;
}
return roundingSide;
}
} }
@@ -0,0 +1,52 @@
package seng302.gameServer.server.messages;
/**
* Created by zyt10 on 10/08/17.
*/
public class YachtEventCodeMessage extends Message {
private final MessageType MESSAGE_TYPE = MessageType.YACHT_EVENT_CODE;
private final int MESSAGE_VERSION = 1; //Always set to 1
private final int MESSAGE_SIZE = 22;
// Message fields
private long timeStamp;
private long ack = 0x00; //Unused
private int raceId;
private int destSourceId;
private int incidentId;
private int eventId;
public YachtEventCodeMessage(Integer subjectId) {
timeStamp = System.currentTimeMillis() / 1000L;
ack = 0;
raceId = 1;
destSourceId = subjectId; // collision boat source id
incidentId = 0;
eventId = 33;
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
// Write message fields
putUnsignedByte((byte) MESSAGE_VERSION);
putInt((int) timeStamp, 6);
putInt((int) ack, 2);
putInt((int) raceId, 4);
putInt((int) destSourceId, 4);
putInt((int) incidentId, 4);
putInt((int) eventId, 1);
writeCRC();
rewind();
}
/**
* @return The length of this message
*/
public int getSize() {
return MESSAGE_SIZE;
}
}
@@ -1,125 +0,0 @@
package seng302.gameServer.server.simulator;
import seng302.model.GeoPoint;
import seng302.utilities.GeoUtility;
public class Boat {
private int sourceID;
private double lat;
private double lng;
private double speed; // in mm/sec
private String boatName, shortName, shorterName;
private boolean isFinished;
private long estimatedTimeTillFinish;
private Corner lastPassedCorner, headingCorner;
public Boat(int sourceID, String boatName) {
this.sourceID = sourceID;
this.boatName = boatName;
this.isFinished = false;
estimatedTimeTillFinish = 0;
}
/**
* Moves boat to the heading direction for a given time duration
* @param heading moving direction in degree.
* @param duration moving duration in millisecond.
*/
public void move(double heading, double duration) {
Double distance = speed * duration / 1000000; // convert mm to meter
GeoPoint originPos = new GeoPoint(lat, lng);
GeoPoint newPos = GeoUtility.getGeoCoordinate(originPos, heading, distance);
this.lat = newPos.getLat();
this.lng = newPos.getLng();
}
public String toString() {
return String.format("Boat (%d): lat: %f, lng: %f", sourceID, lat, lng);
}
public int getSourceID() {
return sourceID;
}
public void setSourceID(int sourceID) {
this.sourceID = sourceID;
}
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public double getLng() {
return lng;
}
public void setLng(double lng) {
this.lng = lng;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
public String getBoatName() {
return boatName;
}
public void setBoatName(String boatName) {
this.boatName = boatName;
}
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
public String getShorterName() {
return shorterName;
}
public void setShorterName(String shorterName) {
this.shorterName = shorterName;
}
public Corner getLastPassedCorner() {
return lastPassedCorner;
}
public void setLastPassedCorner(Corner lastPassedCorner) {
this.lastPassedCorner = lastPassedCorner;
}
public Corner getHeadingCorner() {
return headingCorner;
}
public void setHeadingCorner(Corner headingCorner) {
this.headingCorner = headingCorner;
}
public boolean isFinished() {
return isFinished;
}
public void setFinished(boolean finished) {
isFinished = finished;
}
public long getEstimatedTimeTillFinish(){
return (long) (-getSpeed()) + System.currentTimeMillis();
}
}
@@ -1,91 +0,0 @@
package seng302.gameServer.server.simulator;
import seng302.model.mark.CompoundMark;
public class Corner {
private int seqID;
private CompoundMark compoundMark;
//private int CompoundMarkID;
private RoundingType roundingType;
private int zoneSize; // size of the zone around a mark in boat-lengths.
// TODO: this shouldn't be used in the future!!!!
private double bearingToNextCorner, distanceToNextCorner;
private Corner nextCorner;
public Corner(int seqID, CompoundMark compoundMark, RoundingType roundingType, int zoneSize) {
this.seqID = seqID;
this.compoundMark = compoundMark;
this.roundingType = roundingType;
this.zoneSize = zoneSize;
}
/**
* Prints out corner's info and its compound mark, good for testing
* @return a string showing its details
*/
@Override
public String toString() {
return String.format("Corner: %d - %s - %d, %s\n",
seqID, roundingType.getType(), zoneSize, compoundMark.toString());
}
public int getSeqID() {
return seqID;
}
public void setSeqID(int seqID) {
this.seqID = seqID;
}
public CompoundMark getCompoundMark() {
return compoundMark;
}
public void setCompoundMark(CompoundMark compoundMark) {
this.compoundMark = compoundMark;
}
public RoundingType getRoundingType() {
return roundingType;
}
public void setRoundingType(RoundingType roundingType) {
this.roundingType = roundingType;
}
public int getZoneSize() {
return zoneSize;
}
public void setZoneSize(int zoneSize) {
this.zoneSize = zoneSize;
}
// TODO: next six setters & getters shouldn't be used in the future.
public double getBearingToNextCorner() {
return bearingToNextCorner;
}
public void setBearingToNextCorner(double bearingToNextCorner) {
this.bearingToNextCorner = bearingToNextCorner;
}
public double getDistanceToNextCorner() {
return distanceToNextCorner;
}
public void setDistanceToNextCorner(double distanceToNextCorner) {
this.distanceToNextCorner = distanceToNextCorner;
}
public Corner getNextCorner() {
return nextCorner;
}
public void setNextCorner(Corner nextCorner) {
this.nextCorner = nextCorner;
}
}
@@ -1,43 +0,0 @@
package seng302.gameServer.server.simulator;
public enum RoundingType {
// the mark should be rounded to port (boat's left)
PORT("Port"),
// the mark should be rounded to starboard (boat's right)
STARBOARD("Stbd"),
// the boat within the compound mark with the SeqID of 1 should be rounded
// to starboard and the boat within the compound mark with the SeqID of 2
// should be rounded to port.
SP("SP"),
// the opposite of SP
PS("PS");
private String type;
RoundingType(String type) {
this.type = type;
}
public String getType() {
return this.type;
}
public static RoundingType typeOf(String type) {
switch (type) {
case "Port":
return PORT;
case "Stbd":
return STARBOARD;
case "SP":
return SP;
case "PS":
return PS;
default:
return null;
}
}
}
@@ -1,19 +0,0 @@
package seng302.gameServer.server.simulator.parsers;
import org.w3c.dom.Document;
/**
* Parses the race xml file to get course details
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public class BoatsParser extends FileParser {
private Document doc;
public BoatsParser(String path) {
super(path);
this.doc = this.parseFile();
}
}
@@ -1,117 +0,0 @@
package seng302.gameServer.server.simulator.parsers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import seng302.model.mark.CompoundMark;
import seng302.gameServer.server.simulator.Corner;
import seng302.model.mark.Mark;
import seng302.gameServer.server.simulator.RoundingType;
/**
* Parses the race xml file to get course details
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public class CourseParser extends FileParser {
private Document doc;
private Map<Integer, CompoundMark> compoundMarksMap;
public CourseParser(String path) {
super(path);
this.doc = this.parseFile();
}
// TODO: should handle error / invalid file gracefully
protected List<Corner> getCourse() {
compoundMarksMap = getCompoundMarks(doc.getDocumentElement());
List<Corner> corners = new ArrayList<>();
NodeList cMarksSequence = doc.getElementsByTagName("Corner");
for (int i = 0; i < cMarksSequence.getLength(); i++) {
corners.add(getCorner(cMarksSequence.item(i)));
}
return corners;
}
private Corner getCorner(Node node) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) node;
Integer seqId = Integer.valueOf(e.getAttribute("SeqID"));
Integer cMarkId = Integer.valueOf(e.getAttribute("CompoundMarkID"));
CompoundMark cMark = compoundMarksMap.get(cMarkId);
RoundingType roundingType = RoundingType.typeOf(e.getAttribute("Rounding"));
Integer zoneSize = Integer.valueOf(e.getAttribute("ZoneSize"));
return new Corner(seqId, cMark, roundingType, zoneSize);
}
return null;
}
private Map<Integer, CompoundMark> getCompoundMarks(Node node) {
Map<Integer, CompoundMark> compoundMarksMap = new HashMap<>();
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
NodeList cMarks = element.getElementsByTagName("CompoundMark");
// loop through all compound marks who are the children of course node
for (int i = 0; i < cMarks.getLength(); i++) {
CompoundMark cMark = getCompoundMark(cMarks.item(i));
if (cMark != null)
compoundMarksMap.put(cMark.getId(), cMark);
}
return compoundMarksMap;
}
return null;
}
private CompoundMark getCompoundMark(Node node) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) node;
Integer markID = Integer.valueOf(e.getAttribute("CompoundMarkID"));
String name = e.getAttribute("Name");
CompoundMark cMark = new CompoundMark(markID, name);
NodeList marks = e.getElementsByTagName("Mark");
for (int i = 0; i < marks.getLength(); i++) {
Mark mark = getMark(marks.item(i));
if (mark != null)
cMark.addSubMarks(mark);
}
return cMark;
}
System.out.println("Failed to create compound mark.");
return null;
}
private Mark getMark(Node node) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) node;
Integer seqId = Integer.valueOf(e.getAttribute("SeqID"));
String name = e.getAttribute("Name");
Double lat = Double.valueOf(e.getAttribute("TargetLat"));
Double lng = Double.valueOf(e.getAttribute("TargetLng"));
Integer sourceId = Integer.valueOf(e.getAttribute("SourceID"));
Mark mark = new Mark(name, lat, lng, sourceId);
mark.setSeqID(seqId);
return mark;
}
System.out.println("Failed to create mark.");
return null;
}
}
@@ -1,51 +0,0 @@
package seng302.gameServer.server.simulator.parsers;
import java.io.InputStream;
import java.io.StringReader;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
/**
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public abstract class FileParser {
private String filePath;
public FileParser() {}
public FileParser(String path) {
this.filePath = path;
}
protected Document parseFile() {
try {
InputStream is = getClass().getResourceAsStream(this.filePath);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(is);
// optional, in order to recover info from broken line.
doc.getDocumentElement().normalize();
return doc;
} catch (Exception e) {
System.out.println("[FileParser] Exception");
return null;
}
}
protected Document parseFile(String xmlString) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xmlString)));
// optional, in order to recover info from broken line.
doc.getDocumentElement().normalize();
return doc;
} catch (Exception e) {
System.out.println("[FileParser] Exception");
}
return null;
}
}
@@ -1,65 +0,0 @@
package seng302.gameServer.server.simulator.parsers;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import seng302.gameServer.server.simulator.Boat;
import seng302.gameServer.server.simulator.Corner;
/**
* Parses the race xml file to get course details
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public class RaceParser extends FileParser {
private Document doc;
private String path;
public RaceParser(String path) {
super(path);
this.path = path;
this.doc = this.parseFile();
}
/**
* Parses race.xml file and returns a list of corner which is the race course.
* @return a list of ordered corner to represent the course.
*/
public List<Corner> getCourse() {
CourseParser cp = new CourseParser(path);
return cp.getCourse();
}
/**
* Parses race.xml file and return a list of boats which will compete in the
* race.
* @return a list of boats that are going to compete in the race.
*/
public List<Boat> getBoats() {
NodeList yachts = doc.getDocumentElement().getElementsByTagName("Yacht");
List<Boat> boats = new ArrayList<>();
for (int i = 0; i < yachts.getLength(); i++) {
boats.add(getBoat(yachts.item(i)));
}
return boats;
}
/**
* Parses a single boat from the given node
* @param node a node within a boat tag
* @return a boat instance parsed from the given node
*/
private Boat getBoat(Node node) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) node;
Integer sourceId = Integer.valueOf(e.getAttribute("SourceID"));
return new Boat(sourceId, "Test Boat");
}
return null;
}
}
@@ -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. * Enum for generating colours.
*/ */
public enum Colors { public enum Colors {
RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE; RED, PERU, GOLD, GREEN, BLUE, PURPLE, DEEPPINK, GRAY;
static Integer index = 0; static Integer index = 0;
public static Color getColor() { public static Color getColor() {
if (index == 6) { if (index == 8) {
index = 0; index = 0;
} }
return Color.valueOf(values()[index++].toString()); return Color.valueOf(values()[index++].toString());
@@ -28,4 +28,9 @@ public class GeoPoint {
public void setLng(double lng) { public void setLng(double lng) {
this.lng = 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 { public class Player {
private Socket socket; private Socket socket;
private Yacht yacht; private ServerYacht yacht;
private Integer lastMarkPassed; private Integer lastMarkPassed;
public Player(Socket socket, Yacht yacht) { public Player(Socket socket, ServerYacht yacht) {
this.socket = socket; this.socket = socket;
this.yacht = yacht; this.yacht = yacht;
} }
@@ -30,7 +30,7 @@ public class Player {
this.lastMarkPassed = lastMarkPassed; this.lastMarkPassed = lastMarkPassed;
} }
public Yacht getYacht() { public ServerYacht getYacht() {
return yacht; return yacht;
} }
@@ -71,8 +71,6 @@ public final class PolarTable {
} catch (IOException e) { } catch (IOException e) {
System.out.println("[PolarTable] IO exception"); System.out.println("[PolarTable] IO exception");
} }
} }
@@ -155,7 +153,6 @@ public final class PolarTable {
public static Double getClosestWindSpeedInPolar(Double thisWindSpeed) { public static Double getClosestWindSpeedInPolar(Double thisWindSpeed) {
Double smallestDif = Double.POSITIVE_INFINITY; Double smallestDif = Double.POSITIVE_INFINITY;
Double closestWind = 0d; Double closestWind = 0d;
for (Double polarWindSpeed : polarTable.keySet()) { for (Double polarWindSpeed : polarTable.keySet()) {
Double difference = Math.abs(polarWindSpeed - thisWindSpeed); Double difference = Math.abs(polarWindSpeed - thisWindSpeed);
if (difference < smallestDif) { if (difference < smallestDif) {
@@ -0,0 +1,395 @@
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 = 0;
private Boolean hasEnteredRoundingZone;
private Mark closestCurrentMark;
private Boolean hasPassedLine;
private Boolean hasPassedThroughGate;
private Boolean finishedRace;
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.hasEnteredRoundingZone = false;
this.hasPassedLine = false;
this.hasPassedThroughGate = false;
this.finishedRace = 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 void setIsFinished(Boolean isFinished) {
finishedRace = isFinished;
}
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 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;
}
}
-449
View File
@@ -1,449 +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 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 static final Double ROUNDING_DISTANCE = 15d; // 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 Long lastMark;
private Long markRoundTime;
private Double distanceToNextMark;
private Long timeTillNext;
private CompoundMark nextMark;
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 hasPassedFirstLine; //The line extrapolated from the next mark to the current mark
private Boolean hasPassedSecondLine; //The line extrapolated from the last mark to the current mark
//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.hasPassedFirstLine = false;
this.hasPassedSecondLine = 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
location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed);
//CHECK FOR MARK ROUNDING
distanceToNextMark = calcDistanceToNextMark();
if (distanceToNextMark < ROUNDING_DISTANCE) {
hasEnteredRoundingZone = true;
}
// 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).
*
* @return A distance in metres. Returns -1 if there is no next mark
*/
public Double calcDistanceToNextMark() {
if (nextMark == null) {
return -1d;
} else 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));
}
}
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) {
System.out.println(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 void setNextMark(CompoundMark nextMark) {
this.nextMark = nextMark;
}
public CompoundMark getNextMark(){
return nextMark;
}
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 getDistanceToNextMark() {
return distanceToNextMark;
}
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);
}
}
public void addLocationListener (YachtLocationListener listener) {
locationListeners.add(listener);
}
}
@@ -1,27 +1,27 @@
package seng302.model.mark; package seng302.model.mark;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import seng302.gameServer.server.messages.RoundingSide;
import seng302.model.GeoPoint;
import seng302.utilities.GeoUtility;
public class CompoundMark { public class CompoundMark {
private int compoundMarkId; private int compoundMarkId;
private String name; private String name;
private List<Mark> marks = new ArrayList<>(); private List<Mark> marks = new ArrayList<>();
private GeoPoint midPoint;
public CompoundMark(int markID, String name) { public CompoundMark(int markID, String name, List<Mark> marks) {
this.compoundMarkId = markID; this.compoundMarkId = markID;
this.name = name; this.name = name;
} this.marks.addAll(marks);
if (marks.size() > 1) {
public void addSubMarks(Mark... marks) { this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
this.marks.addAll(Arrays.asList(marks)); } else {
} this.midPoint = marks.get(0);
}
public void addSubMarks(List<Mark> marks) {
this.marks.addAll(marks);
} }
/** /**
@@ -55,6 +55,27 @@ public class CompoundMark {
this.name = name; 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; * 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. * @param singleMarkId the id of the desired mark contained in this compound mark.
@@ -68,6 +89,16 @@ public class CompoundMark {
} }
} }
/**
* NOTE: This is a 'dirty' mid point as it is simply calculated as an xy point would be.
* NO CHECKING FOR LAT / LNG WRAPPING IS DONE IN CREATION OF THIS MIDPOINT
*
* @return GeoPoint of the midpoint of the two marks, or the one mark if there is only one
*/
public GeoPoint getMidPoint() {
return midPoint;
}
/** /**
* Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a * Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a
* specific singleMark or the list of marks. * specific singleMark or the list of marks.
@@ -87,38 +118,6 @@ public class CompoundMark {
return marks; return marks;
} }
// @Override
// public boolean equals(Object other) {
// if (other == null) {
// return false;
// }
//
// if (!(other instanceof Mark)){
// return false;
// }
//
// Mark otherMark = (Mark) other;
//
// if (otherMark.getLat() != getLat() || otherMark.getLongitude() != getLongitude()) {
// return false;
// }
//
// if (otherMark.getCompoundMarkID() != getCompoundMarkID()){
// return false;
// }
//
// if (otherMark.getId() != getId()){
// return false;
// }
//
// if (!otherMark.getName().equals(name)){
// return false;
// }
//
// return true;
// }
@Override @Override
public int hashCode() { public int hashCode() {
int hash = 0; int hash = 0;
+12 -5
View File
@@ -2,6 +2,7 @@ package seng302.model.mark;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import seng302.gameServer.server.messages.RoundingSide;
import seng302.model.GeoPoint; import seng302.model.GeoPoint;
/** /**
@@ -19,11 +20,13 @@ public class Mark extends GeoPoint {
private String name; private String name;
private int sourceID; private int sourceID;
private List<PositionListener> positionListeners = new ArrayList<>(); 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); super(lat, lng);
this.name = name; this.name = name;
this.sourceID = sourceID; this.sourceID = sourceID;
this.seqID = seqID;
} }
/** /**
@@ -39,10 +42,6 @@ public class Mark extends GeoPoint {
return seqID; return seqID;
} }
public void setSeqID(int seqID) {
this.seqID = seqID;
}
public String getName() { public String getName() {
return name; return name;
} }
@@ -55,6 +54,14 @@ public class Mark extends GeoPoint {
return sourceID; return sourceID;
} }
public RoundingSide getRoundingSide() {
return roundingSide;
}
public void setRoundingSide(RoundingSide roundingSide) {
this.roundingSide = roundingSide;
}
public void setSourceID(int sourceID) { public void setSourceID(int sourceID) {
this.sourceID = sourceID; this.sourceID = sourceID;
} }
+43 -41
View File
@@ -1,31 +1,29 @@
package seng302.model.mark; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import seng302.gameServer.server.messages.RoundingSide;
import seng302.model.stream.xml.generator.Race; import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.parser.RaceXMLData; import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.utilities.XMLGenerator; import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser; import seng302.utilities.XMLParser;
import java.util.*;
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;
/** /**
* Class to hold the order of the marks in the race. * Class to hold the order of the marks in the race.
*/ */
public class MarkOrder { public class MarkOrder {
private List<Mark> raceMarkOrder; private List<CompoundMark> raceMarkOrder;
private Logger logger = LoggerFactory.getLogger(MarkOrder.class); private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
private Set<Mark> allMarks;
public MarkOrder(){ public MarkOrder(){
loadRaceProperties(); loadRaceProperties();
@@ -35,7 +33,7 @@ public class MarkOrder {
* @return An ordered list of marks in the race * @return An ordered list of marks in the race
* OR null if the mark order could not be loaded * OR null if the mark order could not be loaded
*/ */
public List<Mark> getMarkOrder(){ public List<CompoundMark> getMarkOrder() {
if (raceMarkOrder == null){ if (raceMarkOrder == null){
logger.warn("Race order accessed but not instantiated"); logger.warn("Race order accessed but not instantiated");
return null; return null;
@@ -45,26 +43,38 @@ public class MarkOrder {
} }
/** /**
* Returns the mark in the race after the previous mark * @param seqID The seqID of the current mark the boat is heading to
* @param position The current race position * @return A Boolean indicating if this coming mark is the last one (finish line)
* @return the next race position
* OR null if there is no position
*/ */
public RacePosition getNextPosition(RacePosition position){ public Boolean isLastMark(Integer seqID) {
Mark previousMark = position.getNextMark(); return seqID == raceMarkOrder.size() - 1;
Mark nextMark; }
if (position.getPositionIndex() + 1 >= raceMarkOrder.size() - 1){ /**
RacePosition nextRacePosition = new RacePosition(raceMarkOrder.size() - 1, null, previousMark); * @param currentSeqID The seqID of the current mark the boat is heading to
nextRacePosition.setFinishingLeg(); * @return The mark last passed
* @throws IndexOutOfBoundsException if there is no next mark. Check seqID != 0 first
*/
public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID - 1);
}
return nextRacePosition; public CompoundMark getCurrentMark(Integer currentSeqID) {
} return raceMarkOrder.get(currentSeqID);
}
Integer nextPositionIndex = position.getPositionIndex() + 1; /**
RacePosition nextRacePosition = new RacePosition(nextPositionIndex, raceMarkOrder.get(nextPositionIndex), previousMark); * @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)}
*/
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID + 1);
}
return nextRacePosition; public Set<Mark> getAllMarks(){
return Collections.unmodifiableSet(allMarks);
} }
/** /**
@@ -72,11 +82,12 @@ public class MarkOrder {
* @param xml An AC35 RaceXML * @param xml An AC35 RaceXML
* @return An ordered list of marks in the race * @return An ordered list of marks in the race
*/ */
private List<Mark> loadRaceOrderFromXML(String xml){ private List<CompoundMark> loadRaceOrderFromXML(String xml) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db; DocumentBuilder db;
Document doc; Document doc;
allMarks = new HashSet<>();
try { try {
db = dbf.newDocumentBuilder(); db = dbf.newDocumentBuilder();
@@ -92,11 +103,13 @@ public class MarkOrder {
logger.debug("Loaded RaceXML for mark order"); logger.debug("Loaded RaceXML for mark order");
List<Corner> corners = data.getMarkSequence(); List<Corner> corners = data.getMarkSequence();
Map<Integer, CompoundMark> marks = data.getCompoundMarks(); Map<Integer, CompoundMark> marks = data.getCompoundMarks();
List<Mark> course = new ArrayList<>(); List<CompoundMark> course = new ArrayList<>();
for (Corner corner : corners){ for (Corner corner : corners){
CompoundMark compoundMark = marks.get(corner.getCompoundMarkID()); CompoundMark compoundMark = marks.get(corner.getCompoundMarkID());
course.add(compoundMark.getMarks().get(0)); compoundMark.setRoundingSide(RoundingSide.getRoundingSide(corner.getRounding()));
course.add(compoundMark);
allMarks.addAll(compoundMark.getMarks());
} }
return course; return course;
@@ -105,17 +118,6 @@ public class MarkOrder {
return null; return null;
} }
/**
* @return The first position in the race
*/
public RacePosition getFirstPosition(){
if (raceMarkOrder.size() > 0){
return new RacePosition(-1, raceMarkOrder.get(0), null);
}
return null;
}
/** /**
* Load the raceXML and mark order * Load the raceXML and mark order
*/ */
@@ -1,55 +0,0 @@
package seng302.model.mark;
/**
* Represents a boats position between two marks
*/
public class RacePosition {
private Integer positionIndex;
private Mark nextMark;
private Mark previousMark;
private Boolean isFinishingLeg;
public RacePosition(Integer positionIndex, Mark nextMark, Mark previousMark){
this.positionIndex = positionIndex;
this.nextMark = nextMark;
this.previousMark = previousMark;
isFinishingLeg = false;
}
/**
* @return The position of the boat (0...number of marks in race - 1)
*/
public Integer getPositionIndex(){
return positionIndex;
}
/**
* @return The mark the boat is heading to
* will return NULL if this is the finishing legg
*/
public Mark getNextMark(){
return nextMark;
}
/**
* @return The mark the boat is heading away from,
* Will return NULL if this is the starting leg
*/
public Mark getPreviousMark(){
return previousMark;
}
/**
* Sets a flag that this is the last leg in the race
*/
public void setFinishingLeg(){
isFinishingLeg = true;
}
/**
* @return true if this is the last leg in the race
*/
public boolean getIsFinishingLeg() {
return isFinishingLeg;
}
}
@@ -19,7 +19,9 @@ public enum PacketType {
COURSE_WIND, COURSE_WIND,
AVG_WIND, AVG_WIND,
BOAT_ACTION, BOAT_ACTION,
OTHER; OTHER,
RACE_REGISTRATION_REQUEST,
RACE_REGISTRATION_RESPONSE;
public static PacketType assignPacketType(int packetType, byte[] payload){ public static PacketType assignPacketType(int packetType, byte[] payload){
switch(packetType){ switch(packetType){
@@ -56,6 +58,10 @@ public enum PacketType {
return AVG_WIND; return AVG_WIND;
case 100: case 100:
return BOAT_ACTION; return BOAT_ACTION;
case 101:
return RACE_REGISTRATION_REQUEST;
case 102:
return RACE_REGISTRATION_RESPONSE;
default: default:
} }
return OTHER; return OTHER;
@@ -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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import seng302.model.Yacht; import seng302.model.ServerYacht;
/** /**
* A Race object that can be parsed into XML * A Race object that can be parsed into XML
*/ */
public class Race { public class Race {
private List<Yacht> yachts;
private List<ServerYacht> yachts;
private LocalDateTime startTime; private LocalDateTime startTime;
public Race(){ public Race(){
@@ -22,7 +23,7 @@ public class Race {
* Add a boat to the race * Add a boat to the race
* @param yacht The boat to add * @param yacht The boat to add
*/ */
public void addBoat(Yacht yacht){ public void addBoat(ServerYacht yacht) {
yachts.add(yacht); yachts.add(yacht);
} }
@@ -30,7 +31,7 @@ public class Race {
* Get a list of boats in the race * Get a list of boats in the race
* @return A List of boats * @return A List of boats
*/ */
public List<Yacht> getBoats(){ public List<ServerYacht> getBoats() {
return Collections.unmodifiableList(yachts); return Collections.unmodifiableList(yachts);
} }
@@ -6,6 +6,7 @@ import seng302.model.GeoPoint;
public class GeoUtility { public class GeoUtility {
private static double EARTH_RADIUS = 6378.137; private static double EARTH_RADIUS = 6378.137;
private static Double MS_TO_KNOTS = 1.943844492;
/** /**
* Calculates the euclidean distance between two markers on the canvas using xy coordinates * Calculates the euclidean distance between two markers on the canvas using xy coordinates
@@ -45,6 +46,19 @@ public class GeoUtility {
return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0; return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0;
} }
/**
* WARNING: this function DOES NOT account for wrapping around on lats / longs etc.
* SO BE CAREFUL IN USING THIS FUNCTION
*
* @param p1 GeoPoint 1
* @param p2 GeoPoint 2
* @return GeoPoint midPoint
*/
public static GeoPoint getDirtyMidPoint(GeoPoint p1, GeoPoint p2) {
return new GeoPoint((p1.getLat() + p2.getLat()) / 2, (p1.getLng() + p2.getLng()) / 2);
}
/** /**
* Calculates the angle between to angular co-ordinates on a sphere in radians. * Calculates the angle between to angular co-ordinates on a sphere in radians.
* *
@@ -93,7 +107,6 @@ public class GeoUtility {
return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng)); return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng));
} }
/** /**
* Performs the line function on two points of a line and a test point to test which side of the * Performs the line function on two points of a line and a test point to test which side of the
* line that point is on. If the return value is return 1, then the point is on one side of the * line that point is on. If the return value is return 1, then the point is on one side of the
@@ -125,6 +138,31 @@ public class GeoUtility {
} }
} }
/**
* Checks if the line formed by lastLocation and location doesn't intersect the line segment
* formed by mark1 and mark2 See the wiki Mark Rounding algorithm for more info
*
* @param mark1 One mark of the line
* @param mark2 The second mark of the line
* @param lastLocation The last location of the point crossing this line
* @param location The current location of the point crossing this line
* @return 0 if two line segment doesn't intersect, otherwise 1 if they intersect and
* lastLocation is on RHS of the line segment (mark1 to mark2) or 2 if lastLocation on LHS of
* the line segment (mark1 to mark2)
*/
public static Integer checkCrossedLine(GeoPoint mark1, GeoPoint mark2, GeoPoint lastLocation,
GeoPoint location) {
boolean enteredDirection = isClockwise(mark1, mark2, lastLocation);
boolean exitedDirection = isClockwise(mark1, mark2, location);
if (enteredDirection != exitedDirection) {
if (!isPointInTriangle(mark1, lastLocation, location, mark2)
&& !isPointInTriangle(mark2, lastLocation, location, mark1)) {
return enteredDirection ? 1 : 2;
}
}
return 0;
}
/** /**
* Given a point and a vector (angle and vector length) Will create a new point, that vector * Given a point and a vector (angle and vector length) Will create a new point, that vector
@@ -155,10 +193,24 @@ public class GeoUtility {
* @param bearing2 the bearing of v2 * @param bearing2 the bearing of v2
* @return the difference of bearing from v1 to v2 * @return the difference of bearing from v1 to v2
*/ */
private static double getBearingDiff(double bearing1, double bearing2) { private static Double getBearingDiff(double bearing1, double bearing2) {
return ((360 - bearing1) + bearing2) % 360; return ((360 - bearing1) + bearing2) % 360;
} }
/**
* Check if a geo point ins on the right hand side of the line segment, which
* formed by two geo points v1 to v2. (Algorithm: point is clockwise to the
* line if the bearing difference is less than 180 deg.)
*
* @param v1 one end of the line segment
* @param v2 another end of the line segment
* @param point the point to be tested
* @return true if the point is on the RHS of the line
*/
public static Boolean isClockwise(GeoPoint v1, GeoPoint v2, GeoPoint point) {
return getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180;
}
/** /**
* Given three geo points to form a triangle, the method returns true if the fourth point is * Given three geo points to form a triangle, the method returns true if the fourth point is
* inside the triangle * inside the triangle
@@ -169,18 +221,34 @@ public class GeoUtility {
* @param point the point to be tested * @param point the point to be tested
* @return true if the fourth point is inside the triangle * @return true if the fourth point is inside the triangle
*/ */
public static boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) { public static Boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) {
// true, if diff of bearing from (v1->v2) to (v1->p) is less than 180 deg // true, if diff of bearing from (v1 to v2) to (v1 to p) is less than 180 deg
boolean sideFlag = getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180; boolean isCW = isClockwise(v1, v2, point);
if ((getBearingDiff(getBearing(v2, v3), getBearing(v2, point)) < 180) != sideFlag) { if (isClockwise(v2, v3, point) != isCW) {
return false; return false;
} }
if ((getBearingDiff(getBearing(v3, v1), getBearing(v3, point)) < 180) != sideFlag) { if (isClockwise(v3, v1, point) != isCW) {
return false; return false;
} }
return true; return true;
} }
/**
* @param boatSpeedInKnots Speed in knots
* @return The Boat speed in millimeters per second
*/
public static Double knotsToMMS(Double boatSpeedInKnots) {
return boatSpeedInKnots / MS_TO_KNOTS * 1000;
}
/**
* @param boatSpeedInMMS Speed in millimeters per second
* @return The Boat speed in knots
*/
public static Double mmsToKnots(Double boatSpeedInMMS) {
return boatSpeedInMMS / 1000 * MS_TO_KNOTS;
}
} }
@@ -13,15 +13,12 @@ import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import seng302.model.stream.packets.PacketType; import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.parser.MarkRoundingData; import seng302.model.stream.parser.*;
import seng302.model.stream.parser.PositionUpdateData;
import seng302.model.stream.parser.PositionUpdateData.DeviceType; import seng302.model.stream.parser.PositionUpdateData.DeviceType;
import seng302.model.stream.parser.RaceStartData;
import seng302.model.stream.parser.RaceStatusData;
/** /**
* StreamParser is a utilities class for taking byte data, formatted according to the AC35 * StreamParser is a utilities class for taking byte data, formatted according to the AC35 streaming
* streaming protocol, and parsing it into basic data types or collections. * protocol, and parsing it into basic data types or collections.
* *
* Created by kre39 on 23/04/17. * Created by kre39 on 23/04/17.
*/ */
@@ -34,8 +31,9 @@ public class StreamParser {
* @return the packet sequence number if the packet is of type HEARTBEAT, null otherwise. * @return the packet sequence number if the packet is of type HEARTBEAT, null otherwise.
*/ */
public static Long extractHeartBeat(StreamPacket packet) { public static Long extractHeartBeat(StreamPacket packet) {
if (packet.getType() != PacketType.HEARTBEAT) if (packet.getType() != PacketType.HEARTBEAT) {
return null; return null;
}
long heartbeat = bytesToLong(packet.getPayload()); long heartbeat = bytesToLong(packet.getPayload());
System.out.println("heartbeat = " + heartbeat); System.out.println("heartbeat = " + heartbeat);
return heartbeat; return heartbeat;
@@ -52,16 +50,17 @@ public class StreamParser {
* containing the parsed packet data. * containing the parsed packet data.
*/ */
public static RaceStatusData extractRaceStatus(StreamPacket packet) { public static RaceStatusData extractRaceStatus(StreamPacket packet) {
if (packet.getType() != PacketType.RACE_STATUS) if (packet.getType() != PacketType.RACE_STATUS) {
return null; return null;
}
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
long currentTime = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); long currentTime = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11)); long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11));
int raceStatus = payload[11]; int raceStatus = payload[11];
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18)); long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload, 12, 18));
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20)); long windDir = bytesToLong(Arrays.copyOfRange(payload, 18, 20));
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22)); long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload, 20, 22));
// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); // DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
// currentTime = format.format((new Date(currentTime))) // currentTime = format.format((new Date(currentTime)))
@@ -70,7 +69,6 @@ public class StreamParser {
windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime
); );
// long timeTillStart = // long timeTillStart =
// ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000; // ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000;
// //
@@ -110,7 +108,7 @@ public class StreamParser {
// boat.setEstimateTimeAtFinish(estTimeAtFinish); // boat.setEstimateTimeAtFinish(estTimeAtFinish);
data.addBoatData(boatID, estTimeAtNextMark, estTimeAtFinish, leg, boatStatus); data.addBoatData(boatID, estTimeAtNextMark, estTimeAtFinish, leg, boatStatus);
} }
return data; return data;
} }
// private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){ // private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
@@ -139,8 +137,9 @@ public class StreamParser {
* DISPLAY_TEXT_MESSAGE. * DISPLAY_TEXT_MESSAGE.
*/ */
public static List<String> extractDisplayMessage(StreamPacket packet) { public static List<String> extractDisplayMessage(StreamPacket packet) {
if (packet.getType() != PacketType.DISPLAY_TEXT_MESSAGE) if (packet.getType() != PacketType.DISPLAY_TEXT_MESSAGE) {
return null; return null;
}
List<String> message = new ArrayList<>(); List<String> message = new ArrayList<>();
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
@@ -166,10 +165,11 @@ public class StreamParser {
* XML_MESSAGE. * XML_MESSAGE.
*/ */
public static Document extractXmlMessage(StreamPacket packet) { public static Document extractXmlMessage(StreamPacket packet) {
if ( packet.getType() != PacketType.RACE_XML && if (packet.getType() != PacketType.RACE_XML &&
packet.getType() != PacketType.REGATTA_XML && packet.getType() != PacketType.REGATTA_XML &&
packet.getType() != PacketType.BOAT_XML ) packet.getType() != PacketType.BOAT_XML) {
return null; return null;
}
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageType = payload[9]; int messageType = payload[9];
@@ -194,8 +194,8 @@ public class StreamParser {
* Extracts the race start status from the packet and returns it as a long array. * Extracts the race start status from the packet and returns it as a long array.
* *
* @param packet Packet parsed in to use the payload * @param packet Packet parsed in to use the payload
* @return An array of form [raceID, raceStartTime, notificationType, timeStamp] or null if * @return An array of form [raceID, raceStartTime, notificationType, timeStamp] or null if the
* the packet type is not of RACE_START_STATUS. * packet type is not of RACE_START_STATUS.
*/ */
public static RaceStartData extractRaceStartStatus(StreamPacket packet) { public static RaceStartData extractRaceStartStatus(StreamPacket packet) {
if (packet.getType() != PacketType.RACE_START_STATUS) { if (packet.getType() != PacketType.RACE_START_STATUS) {
@@ -212,23 +212,25 @@ public class StreamParser {
/** /**
* Parses the the byte array in a StreamPacket for yacht events to retrieve the necessary info * Parses the the byte array in a StreamPacket for yacht events to retrieve the necessary info
* and returns it a an array of longs. * and returns it as YachtEventData.
* *
* @param packet Packet parsed in to use the payload * @param packet Packet parsed in to use the payload
* @return the event data in the form [boatID, incidentID, eventID, timeStamp]. Returns null if * @return the event data in the form of YachtEventData. Returns null if the packet is not of
* the packet is not of type YACHT_EVENT_CODE. * type YACHT_EVENT_CODE.
*/ */
public static long[] extractYachtEventCode(StreamPacket packet) { public static YachtEventData extractYachtEventCode(StreamPacket packet) {
if (packet.getType() != PacketType.YACHT_EVENT_CODE) if (packet.getType() != PacketType.YACHT_EVENT_CODE) {
return null; return null;
}
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long ackNumber = bytesToLong(Arrays.copyOfRange(payload, 7, 9));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13)); long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17)); long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 17, 21)); long incidentId = bytesToLong(Arrays.copyOfRange(payload, 17, 21));
int eventId = payload[21]; int eventId = payload[21];
return new long[] {subjectId, incidentId, eventId, timeStamp}; return new YachtEventData(subjectId, incidentId, eventId, timeStamp);
} }
/** /**
@@ -239,15 +241,16 @@ public class StreamParser {
* Returns null if the packet is not of type YACHT_ACTION_CODE. * Returns null if the packet is not of type YACHT_ACTION_CODE.
*/ */
public static long[] extractYachtActionCode(StreamPacket packet) { public static long[] extractYachtActionCode(StreamPacket packet) {
if (packet.getType() != PacketType.YACHT_ACTION_CODE) if (packet.getType() != PacketType.YACHT_ACTION_CODE) {
return null; return null;
}
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 9, 13)); long subjectId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 13, 17)); long incidentId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
int eventId = payload[17]; int eventId = payload[17];
return new long[] {subjectId, incidentId, eventId, timeStamp}; return new long[]{subjectId, incidentId, eventId, timeStamp};
} }
/** /**
@@ -258,8 +261,9 @@ public class StreamParser {
* CHATTER_TEXT. * CHATTER_TEXT.
*/ */
public static String extractChatterText(StreamPacket packet) { public static String extractChatterText(StreamPacket packet) {
if (packet.getType() != PacketType.CHATTER_TEXT) if (packet.getType() != PacketType.CHATTER_TEXT) {
return null; return null;
}
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
int messageType = payload[1]; int messageType = payload[1];
@@ -276,8 +280,9 @@ public class StreamParser {
* is not of type BOAT_LOCATION. * is not of type BOAT_LOCATION.
*/ */
public static PositionUpdateData extractBoatLocation(StreamPacket packet) { public static PositionUpdateData extractBoatLocation(StreamPacket packet) {
if (packet.getType() != PacketType.BOAT_LOCATION) if (packet.getType() != PacketType.BOAT_LOCATION) {
return null; return null;
}
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int deviceType = (int) payload[15]; int deviceType = (int) payload[15];
long timeValid = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); long timeValid = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
@@ -293,10 +298,11 @@ public class StreamParser {
double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0; double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0;
DeviceType type; DeviceType type;
if (deviceType == 1) if (deviceType == 1) {
type = DeviceType.YACHT_TYPE; type = DeviceType.YACHT_TYPE;
else } else {
type = DeviceType.MARK_TYPE; type = DeviceType.MARK_TYPE;
}
return new PositionUpdateData((int) boatId, type, lat, lon, heading, groundSpeed); return new PositionUpdateData((int) boatId, type, lat, lon, heading, groundSpeed);
} }
@@ -309,8 +315,9 @@ public class StreamParser {
* if packet is not of type MARK_ROUNDING. * if packet is not of type MARK_ROUNDING.
*/ */
public static MarkRoundingData extractMarkRounding(StreamPacket packet) { public static MarkRoundingData extractMarkRounding(StreamPacket packet) {
if (packet.getType() != PacketType.MARK_ROUNDING) if (packet.getType() != PacketType.MARK_ROUNDING) {
return null; return null;
}
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
@@ -325,16 +332,17 @@ public class StreamParser {
} }
/** /**
* Returns a list containing the string value of data within the given stream packet for * Returns a list containing the string value of data within the given stream packet for course
* course wind. * wind.
* *
* @param packet The packet containing the payload * @param packet The packet containing the payload
* @return the string values of the wind packet. Returns null if the packet is not of type * @return the string values of the wind packet. Returns null if the packet is not of type
* COURSE_WIND. * COURSE_WIND.
*/ */
public static List<String> extractCourseWind(StreamPacket packet) { public static List<String> extractCourseWind(StreamPacket packet) {
if (packet.getType() != PacketType.COURSE_WIND) if (packet.getType() != PacketType.COURSE_WIND) {
return null; return null;
}
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
int selectedWindId = payload[1]; int selectedWindId = payload[1];
@@ -366,13 +374,13 @@ public class StreamParser {
* Returns the parsed data from a StreamPacket for average wind data. * Returns the parsed data from a StreamPacket for average wind data.
* *
* @param packet The packet containing the payload * @param packet The packet containing the payload
* @return The wind data in the form * @return The wind data in the form [rawPeriod, rawSamplePeriod, period2, speed2, period3,
* [rawPeriod, rawSamplePeriod, period2, speed2, period3, speed3, period4, speed4, timestamp] * speed3, period4, speed4, timestamp] or null if the packet is not of type AVG_WIND.
* or null if the packet is not of type AVG_WIND.
*/ */
public static long[] extractAvgWind(StreamPacket packet) { public static long[] extractAvgWind(StreamPacket packet) {
if (packet.getType() != PacketType.AVG_WIND) if (packet.getType() != PacketType.AVG_WIND) {
return null; return null;
}
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
@@ -384,7 +392,7 @@ public class StreamParser {
long speed3 = bytesToLong(Arrays.copyOfRange(payload, 17, 19)); long speed3 = bytesToLong(Arrays.copyOfRange(payload, 17, 19));
long period4 = bytesToLong(Arrays.copyOfRange(payload, 19, 21)); long period4 = bytesToLong(Arrays.copyOfRange(payload, 19, 21));
long speed4 = bytesToLong(Arrays.copyOfRange(payload, 21, 23)); long speed4 = bytesToLong(Arrays.copyOfRange(payload, 21, 23));
return new long[] { return new long[]{
rawPeriod, rawSamplePeriod, period2, speed2, period3, speed3, period4, speed4, timeStamp rawPeriod, rawSamplePeriod, period2, speed2, period3, speed3, period4, speed4, timeStamp
}; };
} }
@@ -410,8 +418,7 @@ public class StreamParser {
} }
/** /**
* takes an array of up to 7 bytes and returns a positive * takes an array of up to 7 bytes and returns a positive long constructed from the input bytes
* long constructed from the input bytes
* *
* @param bytes the byte array to conver to Long * @param bytes the byte array to conver to Long
* @return a positive long if there is less than 7 bytes -1 otherwise * @return a positive long if there is less than 7 bytes -1 otherwise
@@ -8,8 +8,8 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import seng302.model.ClientYacht;
import seng302.model.Limit; import seng302.model.Limit;
import seng302.model.Yacht;
import seng302.model.mark.CompoundMark; import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner; import seng302.model.mark.Corner;
import seng302.model.mark.Mark; import seng302.model.mark.Mark;
@@ -125,8 +125,8 @@ public class XMLParser {
* @param doc XML Document Object * @param doc XML Document Object
* @return Mapping of sourceIds to Boats. * @return Mapping of sourceIds to Boats.
*/ */
public static Map<Integer, Yacht> parseBoats(Document doc){ public static Map<Integer, ClientYacht> parseBoats(Document doc) {
Map<Integer, Yacht> competingBoats = new HashMap<>(); Map<Integer, ClientYacht> competingBoats = new HashMap<>();
Element docEle = doc.getDocumentElement(); Element docEle = doc.getDocumentElement();
@@ -135,7 +135,8 @@ public class XMLParser {
Node currentBoat = boatsList.item(i); Node currentBoat = boatsList.item(i);
if (currentBoat.getNodeName().equals("Boat")) { if (currentBoat.getNodeName().equals("Boat")) {
// Boat boat = new Boat(currentBoat); // Boat boat = new Boat(currentBoat);
Yacht yacht = new Yacht(XMLParser.getNodeAttributeString(currentBoat, "Type"), ClientYacht yacht = new ClientYacht(
XMLParser.getNodeAttributeString(currentBoat, "Type"),
XMLParser.getNodeAttributeInt(currentBoat, "SourceID"), XMLParser.getNodeAttributeInt(currentBoat, "SourceID"),
XMLParser.getNodeAttributeString(currentBoat, "HullNum"), XMLParser.getNodeAttributeString(currentBoat, "HullNum"),
XMLParser.getNodeAttributeString(currentBoat, "ShortName"), XMLParser.getNodeAttributeString(currentBoat, "ShortName"),
@@ -256,9 +257,9 @@ public class XMLParser {
if (cMarkNode.getNodeName().equals("CompoundMark")) { if (cMarkNode.getNodeName().equals("CompoundMark")) {
cMark = new CompoundMark( cMark = new CompoundMark(
XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"), XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"),
XMLParser.getNodeAttributeString(cMarkNode, "Name") XMLParser.getNodeAttributeString(cMarkNode, "Name"),
createMarks(cMarkNode)
); );
cMark.addSubMarks(createMarks(cMarkNode));
allMarks.add(cMark); allMarks.add(cMark);
} }
} }
@@ -277,11 +278,12 @@ public class XMLParser {
for (int i = 0; i < childMarks.getLength(); i++) { for (int i = 0; i < childMarks.getLength(); i++) {
Node markNode = childMarks.item(i); Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) { if (markNode.getNodeName().equals("Mark")) {
Integer seqID = XMLParser.getNodeAttributeInt(markNode, "SeqID");
Integer sourceID = XMLParser.getNodeAttributeInt(markNode, "SourceID"); Integer sourceID = XMLParser.getNodeAttributeInt(markNode, "SourceID");
String markName = XMLParser.getNodeAttributeString(markNode, "Name"); String markName = XMLParser.getNodeAttributeString(markNode, "Name");
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "TargetLat"); Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "TargetLat");
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "TargetLng"); Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "TargetLng");
Mark mark = new Mark(markName, targetLat, targetLng, sourceID); Mark mark = new Mark(markName, seqID, targetLat, targetLng, sourceID);
subMarks.add(mark); subMarks.add(mark);
} }
} }
@@ -7,17 +7,28 @@ import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import java.util.zip.Checksum; import java.util.zip.Checksum;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.scene.control.Alert; import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import seng302.model.stream.packets.StreamPacket; import javafx.scene.control.ButtonType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.server.messages.BoatAction;
import seng302.gameServer.server.messages.BoatActionMessage; import seng302.gameServer.server.messages.BoatActionMessage;
import seng302.gameServer.server.messages.ClientType;
import seng302.gameServer.server.messages.Message; import seng302.gameServer.server.messages.Message;
import seng302.gameServer.server.messages.RegistrationRequestMessage;
import seng302.gameServer.server.messages.RegistrationResponseStatus;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
/** /**
* A class describing a single connection to a Server for the purposes of sending and receiving on * A class describing a single connection to a Server for the purposes of sending and receiving on
@@ -25,6 +36,8 @@ import seng302.gameServer.server.messages.Message;
*/ */
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.
*/ */
@@ -47,9 +60,17 @@ public class ClientToServerThread implements Runnable {
private Socket socket; private Socket socket;
private InputStream is; private InputStream is;
private OutputStream os;
private int clientId; private Logger logger = LoggerFactory.getLogger(ClientToServerThread.class);
//Output stream
private OutputStream os;
private Timer upWindPacketTimer = new Timer();
private Timer downWindPacketTimer = new Timer();
private boolean upwindTimerFlag = false, downwindTimerFlag = false;
static public final int PACKET_SENDING_INTERVAL_MS = 100;
private int clientId = -1;
// private Boolean updateClient = true; // private Boolean updateClient = true;
private ByteArrayOutputStream crcBuffer; private ByteArrayOutputStream crcBuffer;
@@ -71,15 +92,8 @@ public class ClientToServerThread implements Runnable {
socket = new Socket(ipAddress, portNumber); socket = new Socket(ipAddress, portNumber);
is = socket.getInputStream(); is = socket.getInputStream();
os = socket.getOutputStream(); os = socket.getOutputStream();
Integer allocatedID = threeWayHandshake();
if (allocatedID != null) { sendRegistrationRequest();
clientId = allocatedID;
clientLog("Successful handshake. Allocated ID: " + clientId, 1);
} else {
clientLog("Unsuccessful handshake", 1);
closeSocket();
return;
}
thread = new Thread(this); thread = new Thread(this);
thread.start(); thread.start();
@@ -128,15 +142,22 @@ public class ClientToServerThread implements Runnable {
if (streamPackets.size() > 0) { if (streamPackets.size() > 0) {
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
} else { } else {
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); if (PacketType.RACE_REGISTRATION_RESPONSE == PacketType.assignPacketType(type, payload)){
for (ClientSocketListener csl : listeners) processRegistrationResponse(new StreamPacket(type, payloadLength, timeStamp, payload));
csl.newPacket(); }
else {
if (clientId == -1) continue; // Do not continue if not registered
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
for (ClientSocketListener csl : listeners)
csl.newPacket();
}
} }
} else { } else {
clientLog("Packet has been dropped", 1); clientLog("Packet has been dropped", 1);
} }
} }
} catch (ByteReadException e) { } catch (ByteReadException e) {
e.printStackTrace();
closeSocket(); closeSocket();
Platform.runLater(() -> { Platform.runLater(() -> {
Alert alert = new Alert(AlertType.ERROR); Alert alert = new Alert(AlertType.ERROR);
@@ -147,7 +168,6 @@ public class ClientToServerThread implements Runnable {
clientLog(e.getMessage(), 1); clientLog(e.getMessage(), 1);
return; return;
} }
// System.out.println("streamPackets = " + streamPackets.size());
} }
closeSocket(); closeSocket();
clientLog("Closed connection to Server", 0); clientLog("Closed connection to Server", 0);
@@ -155,43 +175,127 @@ public class ClientToServerThread implements Runnable {
/** /**
* Listens for an allocated sourceID and returns it to the server * Sends a request to the server asking for a source ID
*
* @return the sourceID allocated to us by the server
*/ */
private Integer threeWayHandshake() { private void sendRegistrationRequest() {
Integer ourSourceID = null; RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER);
while (true) {
try { try {
ourSourceID = is.read(); os.write(requestMessage.getBuffer());
} catch (IOException e) { } catch (IOException e) {
clientLog("Three way handshake failed", 1); logger.error("Could not send registration request. Exiting");
} System.exit(1);
if (ourSourceID != null) {
try {
os.write(ourSourceID);
return ourSourceID;
} catch (IOException e) {
clientLog("Three way handshake failed", 1);
return null;
}
}
} }
} }
/** /**
* Send the post-start race course information * Accepts a response to the registration request message, and updates the client OR quits
* @param boatActionMessage The message to send * @param packet The registration requests packet
*/ */
public void sendBoatActionMessage(BoatActionMessage boatActionMessage) { private void processRegistrationResponse(StreamPacket packet){
try { int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 3));
os.write(boatActionMessage.getBuffer()); int statusCode = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 4,5));
} catch (IOException e) {
clientLog("Could not write to server", 1); RegistrationResponseStatus status = RegistrationResponseStatus.getResponseStatus(statusCode);
if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){
clientId = sourceId;
return;
}
logger.error("Server Denied Connection, Exiting");
final String alertErrorText;
if (status.equals(RegistrationResponseStatus.FAILURE_FULL)){
alertErrorText = "Server is full";
}
else{
alertErrorText = "Could not connect to server";
}
Platform.runLater(() -> {
new Alert(AlertType.ERROR, alertErrorText, ButtonType.OK).showAndWait();
System.exit(1);
});
}
/**
* Sends packets for the given boat action. Special cases are: \n
* - DOWNWIND = Packets are sent every ClientToServerThread.PACKET_SENDING_INTERVAL_MS
* - UPWIND = Packets are sent every ClientToServerThread.PACKET_SENDING_INTERVAL_MS
* - MAINTAIN_HEADING = DOWNWIND and UPWIND packets stop being sent.
* @param actionType The boat action that will dictate packets sent.
*/
public void sendBoatAction(BoatAction actionType) {
switch (actionType) {
case MAINTAIN_HEADING:
if (upwindTimerFlag) {
cancelTimer(upWindPacketTimer);
upwindTimerFlag = false;
upWindPacketTimer = new Timer();
}
if (downwindTimerFlag) {
cancelTimer(downWindPacketTimer);
downwindTimerFlag = false;
downWindPacketTimer = new Timer();
}
break;
case DOWNWIND:
if (!downwindTimerFlag) {
downwindTimerFlag = true;
downWindPacketTimer.scheduleAtFixedRate(
new TimerTask() {
@Override
public void run() {
sendBoatAction(new BoatActionMessage(BoatAction.DOWNWIND));
}
}, 0, PACKET_SENDING_INTERVAL_MS
);
}
break;
case UPWIND:
if (!upwindTimerFlag) {
upwindTimerFlag = true;
upWindPacketTimer.scheduleAtFixedRate(
new TimerTask() {
@Override
public void run() {
sendBoatAction(new BoatActionMessage(BoatAction.UPWIND));
}
}, 0, PACKET_SENDING_INTERVAL_MS
);
}
break;
default:
sendBoatAction(new BoatActionMessage(actionType));
break;
} }
} }
/**
* Cancels a packet sending timer.
* @param timer The timer to cancel.
*/
private void cancelTimer (Timer timer) {
timer.cancel();
timer.purge();
}
/**
* Sends a boat action of the given message type.
* @param message The given message type.
*/
private void sendBoatAction(BoatActionMessage message) {
if (clientId != -1) {
try {
os.write(message.getBuffer());
} catch (IOException e) {
clientLog("Could not write to server", 1);
}
}
}
private void closeSocket() { private void closeSocket() {
try { try {
@@ -245,11 +349,8 @@ public class ClientToServerThread implements Runnable {
} }
} }
public Thread getThread() {
return thread;
}
public int getClientId () { public int getClientId () {
return clientId; return clientId;
} }
} }
+122 -76
View File
@@ -13,17 +13,17 @@ import javafx.scene.Node;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import seng302.gameServer.MainServerThread; import seng302.gameServer.MainServerThread;
import seng302.gameServer.server.messages.BoatAction;
import seng302.model.ClientYacht;
import seng302.model.RaceState; import seng302.model.RaceState;
import seng302.model.Yacht;
import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.parser.MarkRoundingData; import seng302.model.stream.parser.MarkRoundingData;
import seng302.model.stream.parser.PositionUpdateData; import seng302.model.stream.parser.PositionUpdateData;
import seng302.model.stream.parser.PositionUpdateData.DeviceType; import seng302.model.stream.parser.PositionUpdateData.DeviceType;
import seng302.model.stream.parser.RaceStatusData; import seng302.model.stream.parser.RaceStatusData;
import seng302.model.stream.parser.YachtEventData;
import seng302.model.stream.xml.parser.RaceXMLData; import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData; import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.gameServer.server.messages.BoatActionMessage;
import seng302.gameServer.server.messages.BoatActionType;
import seng302.utilities.StreamParser; import seng302.utilities.StreamParser;
import seng302.utilities.XMLParser; import seng302.utilities.XMLParser;
import seng302.visualiser.controllers.LobbyController; import seng302.visualiser.controllers.LobbyController;
@@ -31,7 +31,8 @@ import seng302.visualiser.controllers.LobbyController.CloseStatus;
import seng302.visualiser.controllers.RaceViewController; import seng302.visualiser.controllers.RaceViewController;
/** /**
* Created by cir27 on 20/07/17. * This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
* with a JavaFX Pane to insert itself into.
*/ */
public class GameClient { public class GameClient {
@@ -41,20 +42,27 @@ public class GameClient {
private RaceViewController raceView; private RaceViewController raceView;
private Map<Integer, Yacht> allBoatsMap; private Map<Integer, ClientYacht> allBoatsMap;
private RegattaXMLData regattaData; private RegattaXMLData regattaData;
private RaceXMLData courseData; private RaceXMLData courseData;
private RaceState raceState = new RaceState(); private RaceState raceState = new RaceState();
private ObservableList<String> clientLobbyList = FXCollections.observableArrayList(); private ObservableList<String> clientLobbyList = FXCollections.observableArrayList();
private long lastSendingTime; /**
private int KEY_STROKE_SENDING_FREQUENCY = 50; * Create an instance of the game client. Does not do anything until run with runAsClient()
* runAsHost().
* @param holder The JavaFX Pane that the visual elements for the race will be inserted into.
*/
public GameClient(Pane holder) { public GameClient(Pane holder) {
this.holderPane = holder; this.holderPane = holder;
} }
/**
* Connect to a game at the given address and starts the visualiser.
* @param ipAddress IP to connect to.
* @param portNumber Port to connect to.
*/
public void runAsClient(String ipAddress, Integer portNumber) { public void runAsClient(String ipAddress, Integer portNumber) {
try { try {
socketThread = new ClientToServerThread(ipAddress, portNumber); socketThread = new ClientToServerThread(ipAddress, portNumber);
@@ -62,6 +70,7 @@ public class GameClient {
ioe.printStackTrace(); ioe.printStackTrace();
System.out.println("Unable to connect to host..."); System.out.println("Unable to connect to host...");
} }
socketThread.addStreamObserver(this::parsePackets); socketThread.addStreamObserver(this::parsePackets);
LobbyController lobbyController = loadLobby(); LobbyController lobbyController = loadLobby();
lobbyController.setPlayerListSource(clientLobbyList); lobbyController.setPlayerListSource(clientLobbyList);
@@ -70,6 +79,11 @@ public class GameClient {
lobbyController.addCloseListener((exitCause) -> this.loadStartScreen()); lobbyController.addCloseListener((exitCause) -> this.loadStartScreen());
} }
/**
* Connect to a game as the host at the given address and starts the visualiser.
* @param ipAddress IP to connect to.
* @param portNumber Port to connect to.
*/
public void runAsHost(String ipAddress, Integer portNumber) { public void runAsHost(String ipAddress, Integer portNumber) {
server = new MainServerThread(); server = new MainServerThread();
try { try {
@@ -89,14 +103,14 @@ public class GameClient {
loadStartScreen(); loadStartScreen();
} }
}); });
server.setGameClient(this);
} }
private void loadStartScreen() { private void loadStartScreen() {
socketThread.setSocketToClose(); socketThread.setSocketToClose();
socketThread = null;
if (server != null) { if (server != null) {
// TODO: 26/07/17 cir27 - handle disconnecting server.terminate();
// server.shutDown();
server = null; server = null;
} }
FXMLLoader fxmlLoader = new FXMLLoader( FXMLLoader fxmlLoader = new FXMLLoader(
@@ -115,7 +129,8 @@ public class GameClient {
* @return the lobby controller. * @return the lobby controller.
*/ */
private LobbyController loadLobby() { private LobbyController loadLobby() {
FXMLLoader fxmlLoader = new FXMLLoader(GameClient.class.getResource("/views/LobbyView.fxml")); FXMLLoader fxmlLoader = new FXMLLoader(
GameClient.class.getResource("/views/LobbyView.fxml"));
try { try {
holderPane.getChildren().clear(); holderPane.getChildren().clear();
holderPane.getChildren().add(fxmlLoader.load()); holderPane.getChildren().add(fxmlLoader.load());
@@ -140,10 +155,24 @@ public class GameClient {
holderPane.getScene().setOnKeyPressed(this::keyPressed); holderPane.getScene().setOnKeyPressed(this::keyPressed);
holderPane.getScene().setOnKeyReleased(this::keyReleased); holderPane.getScene().setOnKeyReleased(this::keyReleased);
raceView = fxmlLoader.getController(); raceView = fxmlLoader.getController();
Yacht player = allBoatsMap.get(socketThread.getClientId()); ClientYacht player = allBoatsMap.get(socketThread.getClientId());
raceView.loadRace(allBoatsMap, courseData, raceState, player); raceView.loadRace(allBoatsMap, courseData, raceState, player);
} }
private void loadFinishScreenView() {
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource("/views/FinishScreenView.fxml"));
try {
final Node finishScreenFX = fxmlLoader.load();
Platform.runLater(() -> {
holderPane.getChildren().clear();
holderPane.getChildren().add(finishScreenFX);
});
} catch (IOException e) {
e.printStackTrace();
}
}
private void parsePackets() { private void parsePackets() {
while (socketThread.getPacketQueue().peek() != null) { while (socketThread.getPacketQueue().peek() != null) {
StreamPacket packet = socketThread.getPacketQueue().poll(); StreamPacket packet = socketThread.getPacketQueue().poll();
@@ -174,17 +203,13 @@ public class GameClient {
break; break;
case BOAT_XML: case BOAT_XML:
System.out.println("GOT SUM BOATS YAY :)");
allBoatsMap = XMLParser.parseBoats( allBoatsMap = XMLParser.parseBoats(
StreamParser.extractXmlMessage(packet) StreamParser.extractXmlMessage(packet)
); );
clientLobbyList.clear(); clientLobbyList.clear();
allBoatsMap.forEach((id, boat) -> { allBoatsMap.forEach((id, boat) ->
clientLobbyList.add(id + " " + boat.getBoatName()); clientLobbyList.add(id + " " + boat.getBoatName())
// System.out.println(id + " " + boat.getBoatName()); );
});
// startRaceIfAllDataReceived();
break; break;
case RACE_START_STATUS: case RACE_START_STATUS:
@@ -198,13 +223,18 @@ public class GameClient {
case MARK_ROUNDING: case MARK_ROUNDING:
updateMarkRounding(StreamParser.extractMarkRounding(packet)); updateMarkRounding(StreamParser.extractMarkRounding(packet));
break; break;
case YACHT_EVENT_CODE:
showCollisionAlert(StreamParser.extractYachtEventCode(packet));
break;
} }
} }
} }
private void startRaceIfAllDataReceived() { private void startRaceIfAllDataReceived() {
if (allXMLReceived() && raceView == null) if (allXMLReceived() && raceView == null) {
loadRaceView(); loadRaceView();
}
} }
private boolean allXMLReceived() { private boolean allXMLReceived() {
@@ -217,8 +247,8 @@ public class GameClient {
private void updatePosition(PositionUpdateData positionData) { private void updatePosition(PositionUpdateData positionData) {
if (positionData.getType() == DeviceType.YACHT_TYPE) { if (positionData.getType() == DeviceType.YACHT_TYPE) {
if (allXMLReceived() && allBoatsMap.containsKey(positionData.getDeviceId())) { if (allXMLReceived() && allBoatsMap.containsKey(positionData.getDeviceId())) {
Yacht yacht = allBoatsMap.get(positionData.getDeviceId()); ClientYacht clientYacht = allBoatsMap.get(positionData.getDeviceId());
yacht.updateLocation(positionData.getLat(), clientYacht.updateLocation(positionData.getLat(),
positionData.getLon(), positionData.getHeading(), positionData.getLon(), positionData.getHeading(),
positionData.getGroundSpeed()); positionData.getGroundSpeed());
} }
@@ -235,11 +265,11 @@ public class GameClient {
*/ */
private void updateMarkRounding(MarkRoundingData roundingData) { private void updateMarkRounding(MarkRoundingData roundingData) {
if (allXMLReceived()) { if (allXMLReceived()) {
Yacht yacht = allBoatsMap.get(roundingData.getBoatId()); ClientYacht clientYacht = allBoatsMap.get(roundingData.getBoatId());
yacht.setMarkRoundingTime(roundingData.getTimeStamp()); clientYacht.setMarkRoundingTime(roundingData.getTimeStamp());
yacht.updateTimeSinceLastMarkProperty( clientYacht.updateTimeSinceLastMarkProperty(
raceState.getRaceTime() - roundingData.getTimeStamp()); raceState.getRaceTime() - roundingData.getTimeStamp());
yacht.setLastMarkRounded( clientYacht.setLastMarkRounded(
courseData.getCompoundMarks().get( courseData.getCompoundMarks().get(
roundingData.getMarkId() roundingData.getMarkId()
) )
@@ -250,21 +280,34 @@ 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());
}
boolean raceFinished = true;
for (ClientYacht yacht : allBoatsMap.values()) {
if (yacht.getBoatStatus() != 3) {
raceFinished = false;
}
}
if (raceFinished == true) {
loadFinishScreenView();
}
for (long[] boatData : data.getBoatData()) { for (long[] boatData : data.getBoatData()) {
Yacht yacht = allBoatsMap.get((int) boatData[0]); ClientYacht clientYacht = allBoatsMap.get((int) boatData[0]);
yacht.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]); clientYacht.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]);
yacht.setEstimateTimeAtFinish(boatData[2]); clientYacht.setEstimateTimeAtFinish(boatData[2]);
int legNumber = (int) boatData[3]; int legNumber = (int) boatData[3];
yacht.setLegNumber(legNumber); clientYacht.setLegNumber(legNumber);
yacht.setBoatStatus((int) boatData[4]); clientYacht.setBoatStatus((int) boatData[4]);
if (legNumber != yacht.getLegNumber()) { if (legNumber != clientYacht.getLegNumber()) {
int placing = 1; int placing = 1;
for (Yacht otherYacht : allBoatsMap.values()) { for (ClientYacht otherClientYacht : allBoatsMap.values()) {
if (otherYacht.getSourceId() != boatData[0] && if (otherClientYacht.getSourceId() != boatData[0] &&
yacht.getLegNumber() <= otherYacht.getLegNumber()) clientYacht.getLegNumber() <= otherClientYacht.getLegNumber())
placing++; placing++;
} }
yacht.setPositionInteger(placing); clientYacht.setPositionInteger(placing);
} }
} }
} }
@@ -279,47 +322,50 @@ public class GameClient {
* Handle the key-pressed event from the text field. * Handle the key-pressed event from the text field.
* @param e The key event triggering this call * @param e The key event triggering this call
*/ */
public void keyPressed(KeyEvent e) { private void keyPressed(KeyEvent e) {
BoatActionMessage boatActionMessage;
long currentTime = System.currentTimeMillis();
if (currentTime - lastSendingTime > KEY_STROKE_SENDING_FREQUENCY) {
lastSendingTime = currentTime;
switch (e.getCode()) {
case SPACE: // align with vmg
boatActionMessage = new BoatActionMessage(BoatActionType.VMG);
socketThread.sendBoatActionMessage(boatActionMessage);
break;
case PAGE_UP: // upwind
boatActionMessage = new BoatActionMessage(BoatActionType.UPWIND);
socketThread.sendBoatActionMessage(boatActionMessage);
break;
case PAGE_DOWN: // downwind
boatActionMessage = new BoatActionMessage(BoatActionType.DOWNWIND);
socketThread.sendBoatActionMessage(boatActionMessage);
break;
case ENTER: // tack/gybe
boatActionMessage = new BoatActionMessage(BoatActionType.TACK_GYBE);
socketThread.sendBoatActionMessage(boatActionMessage);
break;
//TODO Allow a zoom in and zoom out methods
case Z: // zoom in
System.out.println("Zoom in");
break;
case X: // zoom out
System.out.println("Zoom out");
break;
}
}
}
public void keyReleased(KeyEvent e) {
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) case SPACE: // align with vmg
case SHIFT: // sails in/sails out socketThread.sendBoatAction(BoatAction.VMG); break;
BoatActionMessage boatActionMessage = new BoatActionMessage( case PAGE_UP: // upwind
BoatActionType.SAILS_IN); socketThread.sendBoatAction(BoatAction.UPWIND); break;
socketThread.sendBoatActionMessage(boatActionMessage); case PAGE_DOWN: // downwind
socketThread.sendBoatAction(BoatAction.DOWNWIND); break;
case ENTER: // tack/gybe
socketThread.sendBoatAction(BoatAction.TACK_GYBE); break;
//TODO Allow a zoom in and zoom out methods
case Z: // zoom in
System.out.println("Zoom in");
break;
case X: // zoom out
System.out.println("Zoom out");
break; break;
} }
} }
private void keyReleased(KeyEvent e) {
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)
case SHIFT: // sails in/sails out
socketThread.sendBoatAction(BoatAction.SAILS_IN);
raceView.getGameView().getPlayerYacht().toggleSail();
break;
case PAGE_UP:
case PAGE_DOWN:
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break;
}
}
public RaceXMLData getCourseData() {
return courseData;
}
/**
* Tells race view to show a collision animation.
*/
private void showCollisionAlert(YachtEventData yachtEventData) {
// 33 is the agreed code to show collision
if (yachtEventData.getEventId() == 33) {
raceView.showCollision(yachtEventData.getSubjectId());
}
}
} }
+159 -62
View File
@@ -9,6 +9,9 @@ import java.util.Map;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import javafx.animation.AnimationTimer; import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
@@ -19,12 +22,14 @@ 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;
import javafx.scene.paint.Paint; import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon; import javafx.scene.shape.Polygon;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import seng302.model.ClientYacht;
import javafx.util.Duration;
import seng302.model.Colors; import seng302.model.Colors;
import seng302.model.GeoPoint; import seng302.model.GeoPoint;
import seng302.model.Limit; import seng302.model.Limit;
import seng302.model.Yacht;
import seng302.model.mark.CompoundMark; import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner; import seng302.model.mark.Corner;
import seng302.model.mark.Mark; import seng302.model.mark.Mark;
@@ -38,10 +43,10 @@ import seng302.visualiser.map.CanvasMap;
*/ */
public class GameView extends Pane { public class GameView extends Pane {
private double bufferSize = 50; private double bufferSize = 50;
private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias. private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias.
private double panelHeight = 960; private double panelHeight = 960;
private double canvasWidth = 1100; private double canvasWidth = 1100;
private double canvasHeight = 920; private double canvasHeight = 920;
private boolean horizontalInversion = false; private boolean horizontalInversion = false;
@@ -51,6 +56,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();
@@ -59,8 +66,8 @@ public class GameView extends Pane {
private List<Limit> borderPoints; private List<Limit> borderPoints;
private Map<Mark, Marker> markerObjects; private Map<Mark, Marker> markerObjects;
private Map<Yacht, BoatObject> boatObjects = new HashMap<>(); private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
private Map<Yacht, AnnotationBox> annotations = new HashMap<>(); private Map<ClientYacht, AnnotationBox> annotations = new HashMap<>();
private ObservableList<Node> gameObjects; private ObservableList<Node> gameObjects;
private Group annotationsGroup = new Group(); private Group annotationsGroup = new Group();
private Group wakesGroup = new Group(); private Group wakesGroup = new Group();
@@ -78,13 +85,33 @@ 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,
VERTICAL VERTICAL
} }
public GameView () { public GameView() {
gameObjects = this.getChildren(); gameObjects = this.getChildren();
// create image view for map, bind panel size to image // create image view for map, bind panel size to image
gameObjects.add(mapImage); gameObjects.add(mapImage);
@@ -97,7 +124,7 @@ public class GameView extends Pane {
initializeTimer(); initializeTimer();
} }
private void initializeTimer () { private void initializeTimer() {
Arrays.fill(frameTimes, 1_000_000_000 / 60); Arrays.fill(frameTimes, 1_000_000_000 / 60);
timer = new AnimationTimer() { timer = new AnimationTimer() {
private long lastTime = 0; private long lastTime = 0;
@@ -133,15 +160,15 @@ public class GameView extends Pane {
} }
} }
// Platform.runLater(() -> // Platform.runLater(() ->
// boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation()) boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation());
// ); // );
} }
}; };
} }
/** /**
* First find the top right and bottom left points' geo locations, then retrieve * First find the top right and bottom left points' geo locations, then retrieve map from google
* map from google to display on image view. - Haoming 22/5/2017 * to display on image view. - Haoming 22/5/2017
*/ */
private void drawGoogleMap() { private void drawGoogleMap() {
findMetersPerPixel(); findMetersPerPixel();
@@ -199,13 +226,23 @@ 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++) {
gates.add( gates.add(
makeAndBindGate( makeAndBindGate(
markerObjects.get(cMark.getSubMark(i)), markerObjects.get(cMark.getSubMark(i)),
markerObjects.get(cMark.getSubMark(i+1)), markerObjects.get(cMark.getSubMark(i + 1)),
colour colour
) )
); );
@@ -269,7 +306,7 @@ public class GameView extends Pane {
gate.endYProperty().bind( gate.endYProperty().bind(
m2.layoutYProperty() m2.layoutYProperty()
); );
return gate; return gate;
} }
/** /**
@@ -307,26 +344,26 @@ public class GameView extends Pane {
/** /**
* Draws all the boats. * Draws all the boats.
* @param yachts The yachts to set in the race * @param clientYachts The yachts to set in the race
*/ */
public void setBoats(List<Yacht> yachts) { public void setBoats(List<ClientYacht> clientYachts) {
BoatObject newBoat; BoatObject newBoat;
final List<Group> wakes = new ArrayList<>(); final List<Group> wakes = new ArrayList<>();
for (Yacht yacht : yachts) { for (ClientYacht clientYacht : clientYachts) {
Paint colour = Colors.getColor(); Paint colour = Colors.getColor();
newBoat = new BoatObject(); newBoat = new BoatObject();
newBoat.setFill(colour); newBoat.setFill(colour);
boatObjects.put(yacht, newBoat); boatObjects.put(clientYacht, newBoat);
createAndBindAnnotationBox(yacht, colour); createAndBindAnnotationBox(clientYacht, colour);
// wakesGroup.getChildren().add(newBoat.getWake()); // wakesGroup.getChildren().add(newBoat.getWake());
wakes.add(newBoat.getWake()); wakes.add(newBoat.getWake());
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.
yacht.addLocationListener((boat, lat, lon, heading, velocity) ->{ clientYacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> {
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);
@@ -347,11 +384,11 @@ public class GameView extends Pane {
}); });
} }
private void createAndBindAnnotationBox (Yacht yacht, Paint colour) { private void createAndBindAnnotationBox(ClientYacht clientYacht, Paint colour) {
AnnotationBox newAnnotation = new AnnotationBox(); AnnotationBox newAnnotation = new AnnotationBox();
newAnnotation.setFill(colour); newAnnotation.setFill(colour);
newAnnotation.addAnnotation( newAnnotation.addAnnotation(
"name", "Player: " + yacht.getShortName() "name", "Player: " + clientYacht.getShortName()
); );
// newAnnotation.addAnnotation( // newAnnotation.addAnnotation(
// "velocity", // "velocity",
@@ -374,28 +411,28 @@ public class GameView extends Pane {
// return format.format(time); // return format.format(time);
// } // }
// ); // );
annotations.put(yacht, newAnnotation); annotations.put(clientYacht, newAnnotation);
} }
private void drawFps(Double fps){ private void drawFps(Double fps) {
Platform.runLater(() -> fpsDisplay.setText(String.format("%d FPS", Math.round(fps)))); Platform.runLater(() -> fpsDisplay.setText(String.format("%d FPS", Math.round(fps))));
} }
/** /**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with
* with the leftmost point, rightmost point, southern most point and northern most point * the leftmost point, rightmost point, southern most point and northern most point
* respectively. * respectively.
*/ */
private void findMinMaxPoint(List<GeoPoint> points) { private void findMinMaxPoint(List<GeoPoint> points) {
List<GeoPoint> sortedPoints = new ArrayList<>(points); List<GeoPoint> sortedPoints = new ArrayList<>(points);
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat)); sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat));
minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng()); minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLat = sortedPoints.get(sortedPoints.size()-1); GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1);
maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng()); maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng());
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng)); sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng));
minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng()); minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLon = sortedPoints.get(sortedPoints.size()-1); GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1);
maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng()); maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng());
if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) { if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) {
horizontalInversion = true; horizontalInversion = true;
@@ -415,15 +452,19 @@ public class GameView extends Pane {
if (scaleDirection == ScaleDirection.HORIZONTAL) { if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs( referenceAngle = Math.abs(
GeoUtility.getBearingRad(referencePoint, minLonPoint) GeoUtility.getBearingRad(referencePoint, minLonPoint)
); );
referencePointX = bufferSize + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility.getDistance(referencePoint, minLonPoint); referencePointX =
bufferSize + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint)); referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint));
referencePointY = canvasHeight - (bufferSize + bufferSize); referencePointY = canvasHeight - (bufferSize + bufferSize);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility.getDistance(referencePoint, maxLatPoint); referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
referencePointY = referencePointY / 2; .getDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += bufferSize; referencePointY += bufferSize;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility.getDistance(referencePoint, maxLatPoint); referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
} else { } else {
referencePointY = canvasHeight - bufferSize; referencePointY = canvasHeight - bufferSize;
referenceAngle = Math.abs( referenceAngle = Math.abs(
@@ -431,11 +472,14 @@ public class GameView extends Pane {
GeoUtility.getDistance(referencePoint, minLonPoint) GeoUtility.getDistance(referencePoint, minLonPoint)
) )
); );
referencePointX = bufferSize; referencePointX = bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility.getDistance(referencePoint, minLonPoint); referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
referencePointX += ((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor)) / 2; .getDistance(referencePoint, minLonPoint);
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
} }
if(horizontalInversion) { if (horizontalInversion) {
referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize); referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize);
} }
} }
@@ -448,12 +492,12 @@ public class GameView extends Pane {
private double scaleRaceExtremities() { private double scaleRaceExtremities() {
double vertAngle = Math.abs( double vertAngle = Math.abs(
GeoUtility.getBearingRad(minLatPoint, maxLatPoint) GeoUtility.getBearingRad(minLatPoint, maxLatPoint)
); );
double vertDistance = double vertDistance =
Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint); Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint);
double horiAngle = Math.abs( double horiAngle = Math.abs(
GeoUtility.getBearingRad(minLonPoint, maxLonPoint) GeoUtility.getBearingRad(minLonPoint, maxLonPoint)
); );
if (horiAngle <= (Math.PI / 2)) { if (horiAngle <= (Math.PI / 2)) {
horiAngle = (Math.PI / 2) - horiAngle; horiAngle = (Math.PI / 2) - horiAngle;
@@ -479,40 +523,45 @@ public class GameView extends Pane {
return findScaledXY(unscaled.getLat(), unscaled.getLng()); return findScaledXY(unscaled.getLat(), unscaled.getLng());
} }
private Point2D findScaledXY (double unscaledLat, double unscaledLon) { private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference; double distanceFromReference;
double angleFromReference; double angleFromReference;
double xAxisLocation = referencePointX; double xAxisLocation = referencePointX;
double yAxisLocation = referencePointY; double yAxisLocation = referencePointY;
angleFromReference = GeoUtility.getBearingRad( angleFromReference = GeoUtility.getBearingRad(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon) minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
); );
distanceFromReference = GeoUtility.getDistance( distanceFromReference = GeoUtility.getDistance(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon) minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
); );
// System.out.println("distanceFromReference = " + distanceFromReference);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) { if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); xAxisLocation += Math
yAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else if (angleFromReference >= 0) { } else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2; angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); xAxisLocation += Math
yAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) { } else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference); angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); xAxisLocation -= Math
yAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else { } else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2; angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); xAxisLocation -= Math
yAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
} }
if(horizontalInversion) { if (horizontalInversion) {
xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize); xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize);
} }
// System.out.println("yAxisLocation = " + yAxisLocation + " " + unscaledLat);
// System.out.println("xAxisLocation = " + xAxisLocation + " " + unscaledLon);
return new Point2D(xAxisLocation, yAxisLocation); return new Point2D(xAxisLocation, yAxisLocation);
} }
@@ -537,7 +586,7 @@ public class GameView extends Pane {
metersPerPixelY = dVertical / dy; metersPerPixelY = dVertical / dy;
} }
public void setAnnotationVisibilities (boolean teamName, boolean velocity, boolean estTime, public void setAnnotationVisibilities(boolean teamName, boolean velocity, boolean estTime,
boolean legTime, boolean trail, boolean wake) { boolean legTime, boolean trail, boolean wake) {
for (BoatObject boatObject : boatObjects.values()) { for (BoatObject boatObject : boatObjects.values()) {
boatObject.setVisibility(teamName, velocity, estTime, legTime, trail, wake); boatObject.setVisibility(teamName, velocity, estTime, legTime, trail, wake);
@@ -550,25 +599,37 @@ public class GameView extends Pane {
} }
} }
public void setFPSVisibility (boolean visibility) { public void setFPSVisibility(boolean visibility) {
fpsDisplay.setVisible(visibility); fpsDisplay.setVisible(visibility);
} }
public void selectBoat (Yacht selectedYacht) { public void selectBoat(ClientYacht selectedClientYacht) {
boatObjects.forEach((boat, group) -> boatObjects.forEach((boat, group) ->
group.setIsSelected(boat == selectedYacht) group.setIsSelected(boat == selectedClientYacht)
); );
} }
public void pauseRace () { public void pauseRace() {
timer.stop(); timer.stop();
} }
public void startRace () {
public void setWindDir(double windDir) {
this.windDir = windDir;
}
public void startRace() {
timer.start(); timer.start();
} }
public void setBoatAsPlayer (Yacht playerYacht) { public ClientYacht getPlayerYacht() {
return playerYacht;
}
public void setBoatAsPlayer (ClientYacht playerYacht) {
this.playerYacht = playerYacht;
this.playerYacht.toggleSail();
boatObjects.get(playerYacht).setAsPlayer(); boatObjects.get(playerYacht).setAsPlayer();
annotations.get(playerYacht).addAnnotation( annotations.get(playerYacht).addAnnotation(
"velocity", "velocity",
@@ -582,4 +643,40 @@ public class GameView extends Pane {
gameObjects.add(annotations.get(playerYacht)); gameObjects.add(annotations.get(playerYacht));
}); });
} }
/**
* Given yacht geopoint by race view controller, drawCollision will calculate canvas X and Y and
* display a flashing red circle on collision point.
*
* @param collisionPoint yacht collision point
*/
public void drawCollision(GeoPoint collisionPoint) {
Platform.runLater(() -> {
Point2D point = findScaledXY(collisionPoint);
double circleRadius = 0.0;
Circle circle = new Circle(point.getX(), point.getY(), circleRadius, Color.RED);
gameObjects.add(circle);
circle.setFill(Color.TRANSPARENT);
circle.setStroke(Color.RED);
circle.setStrokeWidth(3);
Timeline timeline = new Timeline();
timeline.setCycleCount(1);
KeyFrame keyframe1 = new KeyFrame(Duration.ZERO,
new KeyValue(circle.radiusProperty(), 0),
new KeyValue(circle.strokeProperty(), Color.TRANSPARENT));
KeyFrame keyFrame2 = new KeyFrame(new Duration(1000),
new KeyValue(circle.radiusProperty(), 50),
new KeyValue(circle.strokeProperty(), Color.RED));
KeyFrame keyFrame3 = new KeyFrame(new Duration(1500),
new KeyValue(circle.strokeProperty(), Color.TRANSPARENT));
timeline.getKeyFrames().addAll(keyframe1, keyFrame2, keyFrame3);
timeline.play();
timeline.setOnFinished(event -> gameObjects.remove(circle));
});
}
} }
@@ -17,24 +17,24 @@ import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import seng302.model.Yacht; import seng302.model.ClientYacht;
public class FinishScreenViewController implements Initializable { public class FinishScreenViewController implements Initializable {
@FXML @FXML
private GridPane finishScreenGridPane; private GridPane finishScreenGridPane;
@FXML @FXML
private TableView<Yacht> finishOrderTable; private TableView<ClientYacht> finishOrderTable;
@FXML @FXML
private TableColumn<Yacht, String> posCol; private TableColumn<ClientYacht, String> posCol;
@FXML @FXML
private TableColumn<Yacht, String> boatNameCol; private TableColumn<ClientYacht, String> boatNameCol;
@FXML @FXML
private TableColumn<Yacht, String> shortNameCol; private TableColumn<ClientYacht, String> shortNameCol;
@FXML @FXML
private TableColumn<Yacht, String> countryCol; private TableColumn<ClientYacht, String> countryCol;
ObservableList<Yacht> data = FXCollections.observableArrayList(); ObservableList<ClientYacht> data = FXCollections.observableArrayList();
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
@@ -61,9 +61,9 @@ public class FinishScreenViewController implements Initializable {
finishOrderTable.refresh(); finishOrderTable.refresh();
} }
public void setFinishers (List<Yacht> participants) { public void setFinishers(List<ClientYacht> participants) {
List<Yacht> sorted = new ArrayList<>(participants); List<ClientYacht> sorted = new ArrayList<>(participants);
sorted.sort(Comparator.comparingInt(Yacht::getPositionInteger)); sorted.sort(Comparator.comparingInt(ClientYacht::getPositionInteger));
finishOrderTable.getItems().setAll(sorted); finishOrderTable.getItems().setAll(sorted);
} }
@@ -4,15 +4,13 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ListView; import javafx.scene.control.TextArea;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import seng302.gameServer.GameStages; import seng302.gameServer.GameStages;
import seng302.gameServer.GameState; import seng302.gameServer.GameState;
@@ -33,28 +31,26 @@ public class LobbyController {
void notify(CloseStatus exitCause); void notify(CloseStatus exitCause);
} }
@FXML
private GridPane lobbyScreen;
@FXML @FXML
private Text lobbyIpText; private Text lobbyIpText;
@FXML @FXML
private Button readyButton; private Button readyButton;
@FXML @FXML
private ListView<String> firstListView; private TextArea playerOneTxt;
@FXML @FXML
private ListView secondListView; private TextArea playerTwoTxt;
@FXML @FXML
private ListView thirdListView; private TextArea playerThreeTxt;
@FXML @FXML
private ListView fourthListView; private TextArea playerFourTxt;
@FXML @FXML
private ListView fifthListView; private TextArea playerFiveTxt;
@FXML @FXML
private ListView sixthListView; private TextArea playerSixTxt;
@FXML @FXML
private ListView seventhListView; private TextArea playerSevenTxt;
@FXML @FXML
private ListView eighthListView; private TextArea playerEightTxt;
@FXML @FXML
private ImageView firstImageView; private ImageView firstImageView;
@FXML @FXML
@@ -72,79 +68,67 @@ public class LobbyController {
@FXML @FXML
private ImageView eighthImageView; private ImageView eighthImageView;
private List<ObservableList<String>> competitors = new ArrayList<>();
private ObservableList<String> firstCompetitor = FXCollections.observableArrayList();
private ObservableList<String> secondCompetitor = FXCollections.observableArrayList();
private ObservableList<String> thirdCompetitor = FXCollections.observableArrayList();
private ObservableList<String> fourthCompetitor = FXCollections.observableArrayList();
private ObservableList<String> fifthCompetitor = FXCollections.observableArrayList();
private ObservableList<String> sixthCompetitor = FXCollections.observableArrayList();
private ObservableList<String> seventhCompetitor = FXCollections.observableArrayList();
private ObservableList<String> eighthCompetitor = FXCollections.observableArrayList();
private List<ImageView> imageViews = new ArrayList<>(); private List<ImageView> imageViews = new ArrayList<>();
private List<ListView> listViews; private List<TextArea> listViews = new ArrayList<>();
private int MAX_NUM_PLAYERS = 8; private int MAX_NUM_PLAYERS = 8;
private List<LobbyCloseListener> lobbyListeners = new ArrayList<>(); private List<LobbyCloseListener> lobbyListeners = new ArrayList<>();
private ObservableList<String> players = FXCollections.observableArrayList(); private ObservableList<String> players;
/**
* Add all FXObjects to lists and initialize images.
*/
public void initialize() { public void initialize() {
imageViews = new ArrayList<>(); Collections.addAll(listViews,
Collections playerOneTxt, playerTwoTxt, playerThreeTxt, playerFourTxt, playerFiveTxt, playerSixTxt,
.addAll(imageViews, firstImageView, secondImageView, thirdImageView, fourthImageView, playerSevenTxt, playerEightTxt
fifthImageView, sixthImageView, seventhImageView, eighthImageView); );
listViews = new ArrayList<>(); Collections.addAll(imageViews,
Collections.addAll(listViews, firstListView, secondListView, thirdListView, fourthListView, fifthListView, firstImageView, secondImageView, thirdImageView, fourthImageView,
sixthListView, seventhListView, eighthListView); fifthImageView, sixthImageView, seventhImageView, eighthImageView
competitors = new ArrayList<>(); );
Collections.addAll(competitors, firstCompetitor, secondCompetitor, thirdCompetitor,
fourthCompetitor, fifthCompetitor, sixthCompetitor, seventhCompetitor, eighthCompetitor);
initialiseImageView(); initialiseImageView();
} }
private void initialiseListView() { /**
listViews.forEach(listView -> listView.getItems().clear()); * Updates player names.
imageViews.forEach(gif -> gif.setVisible(false)); */
competitors.forEach(ol -> ol.removeAll()); private void updatePlayers() {
//Update players if one added.
for (int i = 0; i < players.size(); i++) { for (int i = 0; i < players.size(); i++) {
competitors.get(i).add(players.get(i)); listViews.get(i).setText(players.get(i));
listViews.get(i).setItems(competitors.get(i));
imageViews.get(i).setVisible(true); imageViews.get(i).setVisible(true);
} }
//Update empty text fields if player left.
for (int i = MAX_NUM_PLAYERS-1; i >= players.size(); i--) {
listViews.get(i).setText("");
imageViews.get(i).setVisible(false);
}
} }
/**
* Sets all images and hides them till players join.
*/
private void initialiseImageView() { private void initialiseImageView() {
imageViews.add(firstImageView); for (ImageView viewer : imageViews) {
imageViews.add(secondImageView); viewer.setImage(
imageViews.add(thirdImageView);
imageViews.add(fourthImageView);
imageViews.add(fifthImageView);
imageViews.add(sixthImageView);
imageViews.add(seventhImageView);
imageViews.add(eighthImageView);
for (int i = 0; i < MAX_NUM_PLAYERS; i++) {
imageViews.get(i).setImage(
new Image( new Image(
RaceViewController.class.getResourceAsStream( RaceViewController.class.getResourceAsStream(
"/pics/sail.png") "/pics/sail.png")
) )
); );
viewer.setVisible(false);
} }
} }
@FXML @FXML
public void leaveLobbyButtonPressed() { public void leaveLobbyButtonPressed() {
// TODO: 10/07/17 wmu16 - Finish function! // TODO: 10/07/17 wmu16 - Finish function!
// setContentPane("/views/StartScreenView.fxml");
GameState.setCurrentStage(GameStages.CANCELLED); GameState.setCurrentStage(GameStages.CANCELLED);
// TODO: 20/07/17 wmu16 - Implement some way of terminating the game // TODO: 20/07/17 wmu16 - Implement some way of terminating the game
// ClientState.setConnectedToHost(false);
for (LobbyCloseListener readyListener : lobbyListeners) for (LobbyCloseListener readyListener : lobbyListeners)
readyListener.notify(CloseStatus.LEAVE); readyListener.notify(CloseStatus.LEAVE);
} }
@FXML @FXML
@@ -154,32 +138,6 @@ public class LobbyController {
readyListener.notify(CloseStatus.READY); readyListener.notify(CloseStatus.READY);
} }
// private static MediaPlayer mediaPlayer;
//
// private void playTheme() {
// Random random = new Random(System.currentTimeMillis());
// Integer rand = random.nextInt();
// if(rand == 10) {
// URL file = getClass().getResource("/music/Disturbed - down with the sickness.mp3");
// Media hit = new Media(file.toString());
// mediaPlayer = new MediaPlayer(hit);
// mediaPlayer.play();
// } else if(rand == 9) {
// URL file = getClass().getResource("/music/Owl City - Fireflies.mp3");
// Media hit = new Media(file.toString());
// mediaPlayer = new MediaPlayer(hit);
// mediaPlayer.play();
// }
// }
// private void switchToRaceView() {
// if (!switchedPane) {
// switchedPane = true;
// setContentPane("/views/RaceView.fxml");
// }
// }
// TODO: 26/07/17 cir27 - Could probably be done in a cleaner way.
public void setTitle (String title) { public void setTitle (String title) {
lobbyIpText.setText(title); lobbyIpText.setText(title);
} }
@@ -191,12 +149,13 @@ public class LobbyController {
public void setPlayerListSource (ObservableList<String> players) { public void setPlayerListSource (ObservableList<String> players) {
this.players = players; this.players = players;
players.addListener((ListChangeListener<? super String>) (lcl) -> players.addListener((ListChangeListener<? super String>) (lcl) ->
Platform.runLater(this::initialiseListView) Platform.runLater(this::updatePlayers)
); );
Platform.runLater(this::initialiseListView); Platform.runLater(this::updatePlayers);
} }
public void disableReadyButton () { public void disableReadyButton () {
readyButton.setDisable(true); readyButton.setDisable(true);
readyButton.setVisible(false);
} }
} }
@@ -34,8 +34,8 @@ import javafx.scene.text.Text;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.StageStyle; import javafx.stage.StageStyle;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import seng302.model.ClientYacht;
import seng302.model.RaceState; import seng302.model.RaceState;
import seng302.model.Yacht;
import seng302.model.mark.CompoundMark; import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark; import seng302.model.mark.Mark;
import seng302.model.stream.xml.parser.RaceXMLData; import seng302.model.stream.xml.parser.RaceXMLData;
@@ -70,16 +70,15 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
@FXML @FXML
private Button selectAnnotationBtn; private Button selectAnnotationBtn;
@FXML @FXML
private ComboBox<Yacht> yachtSelectionComboBox; private ComboBox<ClientYacht> yachtSelectionComboBox;
//Race Data //Race Data
private Map<Integer, Yacht> participants; private Map<Integer, ClientYacht> participants;
private Map<Integer, CompoundMark> markers; private Map<Integer, CompoundMark> markers;
private RaceXMLData courseData; private RaceXMLData courseData;
private GameView gameView; private GameView gameView;
private RaceState raceState; private RaceState raceState;
private Timeline timerTimeline;
private Timer timer = new Timer(); private Timer timer = new Timer();
private List<Series<String, Double>> sparkLineData = new ArrayList<>(); private List<Series<String, Double>> sparkLineData = new ArrayList<>();
private ImportantAnnotationsState importantAnnotations; private ImportantAnnotationsState importantAnnotations;
@@ -101,7 +100,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} }
public void loadRace ( public void loadRace (
Map<Integer, Yacht> participants, RaceXMLData raceData, RaceState raceState, Yacht player Map<Integer, ClientYacht> participants, RaceXMLData raceData, RaceState raceState,
ClientYacht player
) { ) {
this.participants = participants; this.participants = participants;
this.courseData = raceData; this.courseData = raceData;
@@ -208,13 +208,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
/** /**
* Used to add any new yachts into the race that may have started late or not have had data received yet * Used to add any new yachts into the race that may have started late or not have had data
* received yet
*/ */
private void updateSparkLine(){ private void updateSparkLine() {
// TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed. // TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed.
// Collect the racing yachts that aren't already in the chart // Collect the racing yachts that aren't already in the chart
sparkLineData.clear(); sparkLineData.clear();
List<Yacht> sparkLineCandidates = new ArrayList<>(participants.values()); List<ClientYacht> sparkLineCandidates = new ArrayList<>(participants.values());
// Create a new data series for new yachts // Create a new data series for new yachts
sparkLineCandidates sparkLineCandidates
.stream() .stream()
@@ -228,29 +229,30 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
1.0 + participants.size() - yacht.getPositionInteger() 1.0 + participants.size() - yacht.getPositionInteger()
) )
); );
sparkLineData.add(yachtData); sparkLineData.add(yachtData);
}); });
// Lambda function to sort the series in order of leg (later legs shown more to the right) // Lambda function to sort the series in order of leg (later legs shown more to the right)
sparkLineData.sort((o1, o2) -> { sparkLineData.sort((o1, o2) -> {
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue()); Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size() - 1).getXValue());
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue()); Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size() - 1).getXValue());
if (leg2 < leg1){ if (leg2 < leg1) {
return 1; return 1;
} else { } else {
return -1; return -1;
} }
}); });
// Adds the new data series to the sparkline (and set the colour of the series) // Adds the new data series to the sparkline (and set the colour of the series)
Platform.runLater(() -> { Platform.runLater(() ->
sparkLineData sparkLineData
.stream() .stream()
.filter(spark -> !raceSparkLine.getData().contains(spark)) .filter(spark -> !raceSparkLine.getData().contains(spark))
.forEach(spark -> { .forEach(spark -> {
raceSparkLine.getData().add(spark); raceSparkLine.getData().add(spark);
spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName())); spark.getNode().lookup(".chart-series-line")
}); .setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
}); })
);
} }
private void initialiseSparkLine() { private void initialiseSparkLine() {
@@ -260,15 +262,15 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
/** /**
* Updates the yachts sparkline of the desired yacht and using the new leg number * Updates the yachts sparkline of the desired yacht and using the new leg number
* @param yacht The yacht to be updated on the sparkline * @param clientYacht The yacht to be updated on the sparkline
* @param legNumber the leg number that the position will be assigned to * @param legNumber the leg number that the position will be assigned to
*/ */
void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){ void updateYachtPositionSparkline(ClientYacht clientYacht, Integer legNumber) {
for (XYChart.Series<String, Double> positionData : sparkLineData) { for (XYChart.Series<String, Double> positionData : sparkLineData) {
positionData.getData().add( positionData.getData().add(
new Data<>( new Data<>(
Integer.toString(legNumber), Integer.toString(legNumber),
1.0 + participants.size() - yacht.getPositionInteger() 1.0 + participants.size() - clientYacht.getPositionInteger()
) )
); );
} }
@@ -284,26 +286,27 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
/** /**
* gets the rgb string of the yachts colour to use for the chart via css * gets the rgb string of the yachts colour to use for the chart via css
*
* @param yachtId id of yacht passed in to get the yachts colour * @param yachtId id of yacht passed in to get the yachts colour
* @return the colour as an rgb string * @return the colour as an rgb string
*/ */
private String getBoatColorAsRGB(String yachtId){ private String getBoatColorAsRGB(String yachtId) {
Color color = participants.get(Integer.valueOf(yachtId)).getColour(); Color color = participants.get(Integer.valueOf(yachtId)).getColour();
if (color == null){ if (color == null) {
return String.format("#%02X%02X%02X",255,255,255); return String.format("#%02X%02X%02X", 255, 255, 255);
} }
return String.format( "#%02X%02X%02X", return String.format("#%02X%02X%02X",
(int)( color.getRed() * 255 ), (int) (color.getRed() * 255),
(int)( color.getGreen() * 255 ), (int) (color.getGreen() * 255),
(int)( color.getBlue() * 255 ) (int) (color.getBlue() * 255)
); );
} }
/** /**
* Initialises a timer which updates elements of the RaceView such as wind direction, yacht * Initialises a timer which updates elements of the RaceView such as wind direction, yacht
* orderings etc.. which are dependent on the info from the stream parser constantly. * orderings etc.. which are dependent on the info from the stream parser constantly. Updates of
* Updates of each of these attributes are called ONCE EACH SECOND * each of these attributes are called ONCE EACH SECOND
*/ */
private void initializeUpdateTimer() { private void initializeUpdateTimer() {
timer.scheduleAtFixedRate(new TimerTask() { timer.scheduleAtFixedRate(new TimerTask() {
@@ -312,15 +315,16 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
updateRaceTime(); updateRaceTime();
updateWindDirection(); updateWindDirection();
updateOrder(); updateOrder();
updateSparkLine(); // updateSparkLine();
} }
}, 0, 1000); }, 0, 1000);
} }
/** /**
* Iterates over all corners until ones SeqID matches with the yachts current leg number. * Iterates over all corners until ones SeqID matches with the yachts current leg number. Then
* Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark * it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark Returns
* Returns null if no next mark found. * null if no next mark found.
*
* @param bg The BoatGroup to find the next mark of * @param bg The BoatGroup to find the next mark of
* @return The next Mark or null if none found * @return The next Mark or null if none found
*/ */
@@ -376,26 +380,25 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
// positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); // positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// list of racing yacht id // list of racing yacht id
List<Yacht> sorted = new ArrayList<>(participants.values()); List<ClientYacht> sorted = new ArrayList<>(participants.values());
sorted.sort(Comparator.comparingInt(Yacht::getPositionInteger)); sorted.sort(Comparator.comparingInt(ClientYacht::getPositionInteger));
List<Text> vboxEntries = new ArrayList<>(); List<Text> vboxEntries = new ArrayList<>();
for (Yacht yacht : sorted) { for (ClientYacht clientYacht : sorted) {
// System.out.println("yacht == null " + String.valueOf(yacht == null)); // System.out.println("yacht == null " + String.valueOf(yacht == null));
if (yacht.getBoatStatus() == 3) { // 3 is finish status if (clientYacht.getBoatStatus() == 3) { // 3 is finish status
Text textToAdd = new Text(yacht.getPositionInteger() + ". " + Text textToAdd = new Text(clientYacht.getPositionInteger() + ". " +
yacht.getShortName() + " (Finished)"); clientYacht.getShortName() + " (Finished)");
textToAdd.setFill(Paint.valueOf("#d3d3d3")); textToAdd.setFill(Paint.valueOf("#d3d3d3"));
vboxEntries.add(textToAdd); vboxEntries.add(textToAdd);
} else { } else {
Text textToAdd = new Text(yacht.getPositionInteger() + ". " + Text textToAdd = new Text(clientYacht.getPositionInteger() + ". " +
yacht.getShortName() + " "); clientYacht.getShortName() + " ");
textToAdd.setFill(Paint.valueOf("#d3d3d3")); textToAdd.setFill(Paint.valueOf("#d3d3d3"));
textToAdd.setStyle(""); textToAdd.setStyle("");
vboxEntries.add(textToAdd); vboxEntries.add(textToAdd);
} }
// System.out.println("finished a loop :))))))))))))");
} }
Platform.runLater(() -> Platform.runLater(() ->
positionVbox.getChildren().setAll(vboxEntries) positionVbox.getChildren().setAll(vboxEntries)
@@ -475,15 +478,17 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} }
private Point2D getPointRotation(Point2D ref, Double distance, Double angle){ private Point2D getPointRotation(Point2D ref, Double distance, Double angle) {
Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle); Double newX = ref.getX() + (ref.getX() + distance - ref.getX()) * Math.cos(angle)
Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle); - (ref.getY() + distance - ref.getY()) * Math.sin(angle);
Double newY = ref.getY() + (ref.getX() + distance - ref.getX()) * Math.sin(angle)
+ (ref.getY() + distance - ref.getY()) * Math.cos(angle);
return new Point2D(newX, newY); return new Point2D(newX, newY);
} }
public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) { public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle); Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle);
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY()); Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
@@ -502,8 +507,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
/** /**
* Initialised the combo box with any yachts currently in the race and adds the required listener * Initialised the combo box with any yachts currently in the race and adds the required
* for the combobox to take action upon selection * listener for the combobox to take action upon selection
*/ */
private void initialiseBoatSelectionComboBox() { private void initialiseBoatSelectionComboBox() {
yachtSelectionComboBox.setItems( yachtSelectionComboBox.setItems(
@@ -540,7 +545,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
TimeUnit.MILLISECONDS.toHours(milliseconds), TimeUnit.MILLISECONDS.toHours(milliseconds),
TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60, //Modulus 60 minutes per hour TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60, //Modulus 60 minutes per hour
TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60 //Modulus 60 seconds per minute TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60 //Modulus 60 seconds per minute
); );
} }
private void setAnnotations(Integer annotationLevel) { private void setAnnotations(Integer annotationLevel) {
@@ -575,9 +580,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
/** /**
* Sets all the annotations of the selected yacht to be visible and all others to be hidden * Sets all the annotations of the selected yacht to be visible and all others to be hidden
* *
* @param yacht The yacht for which we want to view all annotations * @param clientYacht The yacht for which we want to view all annotations
*/ */
private void setSelectedBoat(Yacht yacht) { private void setSelectedBoat(ClientYacht clientYacht) {
// for (BoatObject bg : gameViewController.getBoatGroups()) { // for (BoatObject bg : gameViewController.getBoatGroups()) {
// //We need to iterate over all race groups to get the matching yacht group belonging to this yacht if we // //We need to iterate over all race groups to get the matching yacht group belonging to this yacht if we
// //are to toggle its annotations, there is no other backwards knowledge of a yacht to its yachtgroup. // //are to toggle its annotations, there is no other backwards knowledge of a yacht to its yachtgroup.
@@ -591,8 +596,23 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
// } // }
} }
public void updateRaceData (RaceXMLData raceData) { public void updateRaceData(RaceXMLData raceData) {
this.courseData = raceData; this.courseData = raceData;
gameView.updateBorder(raceData.getCourseLimit()); gameView.updateBorder(raceData.getCourseLimit());
} }
/**
* Called by game client after receiving yacht event packet. Parameter subject id is the
* offending yacht. This function in turn will pass the yacht location to game view to display a
* collision alert.
*
* @param subjectId source id of offending yacht
*/
public void showCollision(Long subjectId) {
gameView.drawCollision(participants.get((int) (long) subjectId).getLocation());
}
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() {
@@ -268,18 +347,19 @@ public class BoatObject extends Group {
*/ */
public void setAsPlayer() { public void setAsPlayer() {
boatPoly.getPoints().setAll( boatPoly.getPoints().setAll(
-BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75, -BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75,
0.0, -BOAT_HEIGHT / 1.75, 0.0, -BOAT_HEIGHT / 1.75,
BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75 BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75
); );
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;
@@ -52,7 +52,7 @@ public class Wake extends Group {
arc.setFill(new Color(0.0, 0.0, 0.0, 0.0)); arc.setFill(new Color(0.0, 0.0, 0.0, 0.0));
arcs[i] = arc; arcs[i] = arc;
arc.getTransforms().setAll( arc.getTransforms().setAll(
new Rotate(1) new Rotate(1)
); );
} }
super.getChildren().addAll(arcs); super.getChildren().addAll(arcs);
@@ -60,15 +60,15 @@ public class Wake extends Group {
void setRotation (double rotation, double velocity) { void setRotation (double rotation, double velocity) {
// if (Math.abs(rotations[0] - rotation) > 20) { // if (Math.abs(rotations[0] - rotation) > 20) {
Platform.runLater(() -> { Platform.runLater(() -> {
rotate(rotation); rotate(rotation);
double rad = (14 / numWakes) + velocity; double rad = (14 / numWakes) + velocity;
for (Arc arc : arcs) { for (Arc arc : arcs) {
arc.setRadiusX(rad); arc.setRadiusX(rad);
arc.setRadiusY(rad); arc.setRadiusY(rad);
rad += (14 / numWakes) + (velocity / 2.5); rad += (14 / numWakes) + (velocity / 2.5);
} }
}); });
// } else { // } else {
// rotations[0] = rotation; // rotations[0] = rotation;
// ((Rotate) arcs[0].getTransforms().get(0)).setAngle(rotation); // ((Rotate) arcs[0].getTransforms().get(0)).setAngle(rotation);
@@ -28,7 +28,7 @@ public class MercatorProjection {
* @param geo GeoPoint (lat, lng) location to be projected * @param geo GeoPoint (lat, lng) location to be projected
* @return the projection Point2D (x, y) on planar * @return the projection Point2D (x, y) on planar
*/ */
static Point2D toMapPoint(GeoPoint geo) { public static Point2D toMapPoint(GeoPoint geo) {
double x, y; double x, y;
Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0); Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
x = (origin.getX() + geo.getLng() * pixelsPerLngDegree); x = (origin.getX() + geo.getLng() * pixelsPerLngDegree);
+1 -1
View File
@@ -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>
+6 -6
View File
@@ -4,37 +4,37 @@
<team> <team>
<name>Oracle Team USA</name> <name>Oracle Team USA</name>
<alias>USA</alias> <alias>USA</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>102</id> <id>102</id>
</team> </team>
<team> <team>
<name>Artemis Racing</name> <name>Artemis Racing</name>
<alias>ART</alias> <alias>ART</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>101</id> <id>101</id>
</team> </team>
<team> <team>
<name>Emirates Team New Zealand</name> <name>Emirates Team New Zealand</name>
<alias>NZL</alias> <alias>NZL</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>103</id> <id>103</id>
</team> </team>
<team> <team>
<name>Land Rover BAR</name> <name>Land Rover BAR</name>
<alias>BAR</alias> <alias>BAR</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>104</id> <id>104</id>
</team> </team>
<team> <team>
<name>SoftBank Team Japan</name> <name>SoftBank Team Japan</name>
<alias>JAP</alias> <alias>JAP</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>105</id> <id>105</id>
</team> </team>
<team> <team>
<name>Groupama Team France</name> <name>Groupama Team France</name>
<alias>FRC</alias> <alias>FRC</alias>
<velocity>0.0</velocity> <currentVelocity>0.0</currentVelocity>
<id>106</id> <id>106</id>
</team> </team>
</teams> </teams>
-171
View File
@@ -1,171 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2015-08-28T17:32:59+0100</Modified>
<Version>12</Version>
<Snapshot>219</Snapshot>
<Settings>
<RaceBoatType Type="AC45"/>
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="53.796"/>
<ZoneLimits Limit1="200" Limit2="100" Limit3="53.796" Limit4="0" Limit5="-100"/>
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="3" Y="25" X="0"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="14">
<Vertices>
<Vtx Seq="1" Y="0" X="-1"/>
<Vtx Seq="2" Y="0.75" X="-1"/>
<Vtx Seq="3" Y="0.75" X="-0.25"/>
<Vtx Seq="4" Y="3.5" X="-0.25"/>
<Vtx Seq="5" Y="4.5" X="-1"/>
<Vtx Seq="6" Y="6.5" X="-1"/>
<Vtx Seq="7" Y="7" X="-0.5"/>
<Vtx Seq="8" Y="7" X="0.5"/>
<Vtx Seq="9" Y="6.5" X="1"/>
<Vtx Seq="10" Y="4.5" X="1"/>
<Vtx Seq="11" Y="3.5" X="0.25"/>
<Vtx Seq="12" Y="0.75" X="0.25"/>
<Vtx Seq="13" Y="0.75" X="1"/>
<Vtx Seq="14" Y="0" X="1"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="15">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46"/>
<Vtx Seq="2" Y="13.449" X="-3.46"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="13.449" X="3.46"/>
<Vtx Seq="5" Y="0" X="3.46"/>
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752"/>
<Vtx Seq="2" Y="0" X="-2.813"/>
<Vtx Seq="3" Y="0" X="-3.34"/>
<Vtx Seq="4" Y="5.351" X="-3.46"/>
<Vtx Seq="5" Y="10.544" X="-3.387"/>
<Vtx Seq="6" Y="13.449" X="-3.075"/>
<Vtx Seq="7" Y="10.851" X="-2.793"/>
<Vtx Seq="8" Y="6.669" X="-2.699"/>
<Vtx Seq="9" Y="6.669" X="2.699"/>
<Vtx Seq="10" Y="10.851" X="2.793"/>
<Vtx Seq="11" Y="13.449" X="3.075"/>
<Vtx Seq="12" Y="10.544" X="3.387"/>
<Vtx Seq="13" Y="5.351" X="3.46"/>
<Vtx Seq="14" Y="0" X="3.34"/>
<Vtx Seq="15" Y="0" X="2.813"/>
<Vtx Seq="16" Y="1.769" X="2.752"/>
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2"/>
<Vtx Seq="2" Y="11.377" X="-0.2"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="11.377" X="0.2"/>
<Vtx Seq="5" Y="6.669" X="0.2"/>
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699"/>
<Vtx Seq="2" Y="6.438" X="-2.699"/>
<Vtx Seq="3" Y="6.438" X="2.699"/>
<Vtx Seq="4" Y="2" X="2.699"/>
</Trampoline>
</BoatShape>
<BoatShape ShapeID="18">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.04"/>
<Vtx Seq="2" Y="0.11" X="-1.18"/>
<Vtx Seq="3" Y="0.42" X="-1.28"/>
<Vtx Seq="4" Y="3.74" X="-1.29"/>
<Vtx Seq="5" Y="5.36" X="-1.21"/>
<Vtx Seq="6" Y="6.29" X="-1.08"/>
<Vtx Seq="7" Y="7.15" X="-0.84"/>
<Vtx Seq="8" Y="7.63" X="-0.62"/>
<Vtx Seq="9" Y="7.94" X="-0.34"/>
<Vtx Seq="10" Y="8.06" X="0"/>
<Vtx Seq="11" Y="7.94" X="0.34"/>
<Vtx Seq="12" Y="7.63" X="0.62"/>
<Vtx Seq="13" Y="7.15" X="0.84"/>
<Vtx Seq="14" Y="6.29" X="1.08"/>
<Vtx Seq="15" Y="5.36" X="1.21"/>
<Vtx Seq="16" Y="3.74" X="1.29"/>
<Vtx Seq="17" Y="0.42" X="1.28"/>
<Vtx Seq="18" Y="0.11" X="1.18"/>
<Vtx Seq="19" Y="0" X="1.04"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="24">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.5"/>
<Vtx Seq="2" Y="7" X="-2.5"/>
<Vtx Seq="3" Y="12.6" X="-2.2"/>
<Vtx Seq="4" Y="12.6" X="2.2"/>
<Vtx Seq="5" Y="7" X="2.5"/>
<Vtx Seq="6" Y="0" X="2.5"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="34">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.16"/>
<Vtx Seq="2" Y="5.51" X="-1.16"/>
<Vtx Seq="3" Y="5.846" X="-0.84"/>
<Vtx Seq="4" Y="5.846" X="0.84"/>
<Vtx Seq="5" Y="5.51" X="1.16"/>
<Vtx Seq="6" Y="0" X="1.16"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="35">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.461"/>
<Vtx Seq="2" Y="6" X="-1.461"/>
<Vtx Seq="3" Y="7" X="-1.44"/>
<Vtx Seq="4" Y="8" X="-1.38"/>
<Vtx Seq="5" Y="9" X="-1.17"/>
<Vtx Seq="6" Y="10" X="-0.76"/>
<Vtx Seq="7" Y="10.6" X="-0.34"/>
<Vtx Seq="8" Y="10.61" X="0"/>
<Vtx Seq="9" Y="10.6" X="0.34"/>
<Vtx Seq="10" Y="10" X="0.76"/>
<Vtx Seq="11" Y="9" X="1.17"/>
<Vtx Seq="12" Y="8" X="1.38"/>
<Vtx Seq="13" Y="7" X="1.44"/>
<Vtx Seq="14" Y="6" X="1.461"/>
<Vtx Seq="15" Y="0" X="1.461"/>
</Vertices>
</BoatShape>
</BoatShapes>
<Boats>
<Boat Type="Yacht" SourceID="201" ShapeID="15" StoweName="USA" ShortName="ORACLE" ShorterName="USA" BoatName="ORACLE TEAM USA" HullNum="AC4515" Skipper="SPITHILL" Helmsman="SPITHILL" Country="USA" PeliID="101" RadioIP="172.20.2.101">
<GPSposition Z="1.78" Y="-0.331" X="-0.006"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="301" ShapeID="15" StoweName="SWE" ShortName="ARTEMIS" ShorterName="SWE" BoatName="ARTEMIS RACING" HullNum="AC4517" Skipper="OUTTERIDGE" Helmsman="OUTTERIDGE" Country="SWE" PeliID="102" RadioIP="172.20.2.102">
<GPSposition Z="1.727" Y="-0.359" X="-0.0121"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="401" ShapeID="15" StoweName="NZL" ShortName="ETNZ" ShorterName="NZL" BoatName="EMIRATES TEAM NZ" HullNum="AC4503" Skipper="ASHBY" Helmsman="BURLING" Country="NZL" PeliID="103" RadioIP="172.20.2.103">
<GPSposition Z="1.881" Y="-0.291" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="501" ShapeID="15" StoweName="JPN" ShortName="JAPAN" ShorterName="JPN" BoatName="SOFTBANK TEAM JAPAN" HullNum="AC4504" Skipper="BARKER" Helmsman="BARKER" Country="JPN" PeliID="104" RadioIP="172.20.2.104">
<GPSposition Z="1.805" Y="-0.322" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="601" ShapeID="15" StoweName="FRA" ShortName="FRANCE" ShorterName="FRA" BoatName="GROUPAMA TEAM FRANCE" HullNum="AC4505" Skipper="CAMMAS" Helmsman="CAMMAS" Country="FRA" PeliID="105" RadioIP="172.20.2.105">
<GPSposition Z="1.863" Y="-0.3" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="701" ShapeID="15" StoweName="GBR" ShortName="GBR" ShorterName="GBR" BoatName="LAND ROVER BAR" HullNum="AC4516" Skipper="ANSLIE" Helmsman="ANSLIE" Country="GBR" PeliID="106" RadioIP="172.20.2.106">
<GPSposition Z="1.734" Y="-0.352" X="0"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
</Boats>
</BoatConfig>
-161
View File
@@ -1,161 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2015-08-28T17:32:59+0100</Modified>
<Version>12</Version>
<Snapshot>219</Snapshot>
<Settings>
<RaceBoatType Type="AC45"/>
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="53.796"/>
<ZoneLimits Limit1="200" Limit2="100" Limit3="53.796" Limit4="0" Limit5="-100"/>
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="3" Y="25" X="0"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="14">
<Vertices>
<Vtx Seq="1" Y="0" X="-1"/>
<Vtx Seq="2" Y="0.75" X="-1"/>
<Vtx Seq="3" Y="0.75" X="-0.25"/>
<Vtx Seq="4" Y="3.5" X="-0.25"/>
<Vtx Seq="5" Y="4.5" X="-1"/>
<Vtx Seq="6" Y="6.5" X="-1"/>
<Vtx Seq="7" Y="7" X="-0.5"/>
<Vtx Seq="8" Y="7" X="0.5"/>
<Vtx Seq="9" Y="6.5" X="1"/>
<Vtx Seq="10" Y="4.5" X="1"/>
<Vtx Seq="11" Y="3.5" X="0.25"/>
<Vtx Seq="12" Y="0.75" X="0.25"/>
<Vtx Seq="13" Y="0.75" X="1"/>
<Vtx Seq="14" Y="0" X="1"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="15">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46"/>
<Vtx Seq="2" Y="13.449" X="-3.46"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="13.449" X="3.46"/>
<Vtx Seq="5" Y="0" X="3.46"/>
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752"/>
<Vtx Seq="2" Y="0" X="-2.813"/>
<Vtx Seq="3" Y="0" X="-3.34"/>
<Vtx Seq="4" Y="5.351" X="-3.46"/>
<Vtx Seq="5" Y="10.544" X="-3.387"/>
<Vtx Seq="6" Y="13.449" X="-3.075"/>
<Vtx Seq="7" Y="10.851" X="-2.793"/>
<Vtx Seq="8" Y="6.669" X="-2.699"/>
<Vtx Seq="9" Y="6.669" X="2.699"/>
<Vtx Seq="10" Y="10.851" X="2.793"/>
<Vtx Seq="11" Y="13.449" X="3.075"/>
<Vtx Seq="12" Y="10.544" X="3.387"/>
<Vtx Seq="13" Y="5.351" X="3.46"/>
<Vtx Seq="14" Y="0" X="3.34"/>
<Vtx Seq="15" Y="0" X="2.813"/>
<Vtx Seq="16" Y="1.769" X="2.752"/>
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2"/>
<Vtx Seq="2" Y="11.377" X="-0.2"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="11.377" X="0.2"/>
<Vtx Seq="5" Y="6.669" X="0.2"/>
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699"/>
<Vtx Seq="2" Y="6.438" X="-2.699"/>
<Vtx Seq="3" Y="6.438" X="2.699"/>
<Vtx Seq="4" Y="2" X="2.699"/>
</Trampoline>
</BoatShape>
<BoatShape ShapeID="18">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.04"/>
<Vtx Seq="2" Y="0.11" X="-1.18"/>
<Vtx Seq="3" Y="0.42" X="-1.28"/>
<Vtx Seq="4" Y="3.74" X="-1.29"/>
<Vtx Seq="5" Y="5.36" X="-1.21"/>
<Vtx Seq="6" Y="6.29" X="-1.08"/>
<Vtx Seq="7" Y="7.15" X="-0.84"/>
<Vtx Seq="8" Y="7.63" X="-0.62"/>
<Vtx Seq="9" Y="7.94" X="-0.34"/>
<Vtx Seq="10" Y="8.06" X="0"/>
<Vtx Seq="11" Y="7.94" X="0.34"/>
<Vtx Seq="12" Y="7.63" X="0.62"/>
<Vtx Seq="13" Y="7.15" X="0.84"/>
<Vtx Seq="14" Y="6.29" X="1.08"/>
<Vtx Seq="15" Y="5.36" X="1.21"/>
<Vtx Seq="16" Y="3.74" X="1.29"/>
<Vtx Seq="17" Y="0.42" X="1.28"/>
<Vtx Seq="18" Y="0.11" X="1.18"/>
<Vtx Seq="19" Y="0" X="1.04"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="24">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.5"/>
<Vtx Seq="2" Y="7" X="-2.5"/>
<Vtx Seq="3" Y="12.6" X="-2.2"/>
<Vtx Seq="4" Y="12.6" X="2.2"/>
<Vtx Seq="5" Y="7" X="2.5"/>
<Vtx Seq="6" Y="0" X="2.5"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="34">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.16"/>
<Vtx Seq="2" Y="5.51" X="-1.16"/>
<Vtx Seq="3" Y="5.846" X="-0.84"/>
<Vtx Seq="4" Y="5.846" X="0.84"/>
<Vtx Seq="5" Y="5.51" X="1.16"/>
<Vtx Seq="6" Y="0" X="1.16"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="35">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.461"/>
<Vtx Seq="2" Y="6" X="-1.461"/>
<Vtx Seq="3" Y="7" X="-1.44"/>
<Vtx Seq="4" Y="8" X="-1.38"/>
<Vtx Seq="5" Y="9" X="-1.17"/>
<Vtx Seq="6" Y="10" X="-0.76"/>
<Vtx Seq="7" Y="10.6" X="-0.34"/>
<Vtx Seq="8" Y="10.61" X="0"/>
<Vtx Seq="9" Y="10.6" X="0.34"/>
<Vtx Seq="10" Y="10" X="0.76"/>
<Vtx Seq="11" Y="9" X="1.17"/>
<Vtx Seq="12" Y="8" X="1.38"/>
<Vtx Seq="13" Y="7" X="1.44"/>
<Vtx Seq="14" Y="6" X="1.461"/>
<Vtx Seq="15" Y="0" X="1.461"/>
</Vertices>
</BoatShape>
</BoatShapes>
<Boats>
<Boat Type="Yacht" SourceID="201" ShapeID="15" StoweName="USA" ShortName="ORACLE" ShorterName="USA" BoatName="ORACLE TEAM USA" HullNum="AC4515" Skipper="SPITHILL" Helmsman="SPITHILL" Country="USA" PeliID="101" RadioIP="172.20.2.101">
<GPSposition Z="1.78" Y="-0.331" X="-0.006"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="401" ShapeID="15" StoweName="NZL" ShortName="ETNZ" ShorterName="NZL" BoatName="EMIRATES TEAM NZ" HullNum="AC4503" Skipper="ASHBY" Helmsman="BURLING" Country="NZL" PeliID="103" RadioIP="172.20.2.103">
<GPSposition Z="1.881" Y="-0.291" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="501" ShapeID="15" StoweName="JPN" ShortName="JAPAN" ShorterName="JPN" BoatName="SOFTBANK TEAM JAPAN" HullNum="AC4504" Skipper="BARKER" Helmsman="BARKER" Country="JPN" PeliID="104" RadioIP="172.20.2.104">
<GPSposition Z="1.805" Y="-0.322" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="701" ShapeID="15" StoweName="GBR" ShortName="GBR" ShorterName="GBR" BoatName="LAND ROVER BAR" HullNum="AC4516" Skipper="ANSLIE" Helmsman="ANSLIE" Country="GBR" PeliID="106" RadioIP="172.20.2.106">
<GPSposition Z="1.734" Y="-0.352" X="0"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
</Boats>
</BoatConfig>
-171
View File
@@ -1,171 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2015-08-28T17:32:59+0100</Modified>
<Version>12</Version>
<Snapshot>219</Snapshot>
<Settings>
<RaceBoatType Type="AC45"/>
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="53.796"/>
<ZoneLimits Limit1="200" Limit2="100" Limit3="53.796" Limit4="0" Limit5="-100"/>
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="3" Y="25" X="0"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="14">
<Vertices>
<Vtx Seq="1" Y="0" X="-1"/>
<Vtx Seq="2" Y="0.75" X="-1"/>
<Vtx Seq="3" Y="0.75" X="-0.25"/>
<Vtx Seq="4" Y="3.5" X="-0.25"/>
<Vtx Seq="5" Y="4.5" X="-1"/>
<Vtx Seq="6" Y="6.5" X="-1"/>
<Vtx Seq="7" Y="7" X="-0.5"/>
<Vtx Seq="8" Y="7" X="0.5"/>
<Vtx Seq="9" Y="6.5" X="1"/>
<Vtx Seq="10" Y="4.5" X="1"/>
<Vtx Seq="11" Y="3.5" X="0.25"/>
<Vtx Seq="12" Y="0.75" X="0.25"/>
<Vtx Seq="13" Y="0.75" X="1"/>
<Vtx Seq="14" Y="0" X="1"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="15">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46"/>
<Vtx Seq="2" Y="13.449" X="-3.46"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="13.449" X="3.46"/>
<Vtx Seq="5" Y="0" X="3.46"/>
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752"/>
<Vtx Seq="2" Y="0" X="-2.813"/>
<Vtx Seq="3" Y="0" X="-3.34"/>
<Vtx Seq="4" Y="5.351" X="-3.46"/>
<Vtx Seq="5" Y="10.544" X="-3.387"/>
<Vtx Seq="6" Y="13.449" X="-3.075"/>
<Vtx Seq="7" Y="10.851" X="-2.793"/>
<Vtx Seq="8" Y="6.669" X="-2.699"/>
<Vtx Seq="9" Y="6.669" X="2.699"/>
<Vtx Seq="10" Y="10.851" X="2.793"/>
<Vtx Seq="11" Y="13.449" X="3.075"/>
<Vtx Seq="12" Y="10.544" X="3.387"/>
<Vtx Seq="13" Y="5.351" X="3.46"/>
<Vtx Seq="14" Y="0" X="3.34"/>
<Vtx Seq="15" Y="0" X="2.813"/>
<Vtx Seq="16" Y="1.769" X="2.752"/>
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2"/>
<Vtx Seq="2" Y="11.377" X="-0.2"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="11.377" X="0.2"/>
<Vtx Seq="5" Y="6.669" X="0.2"/>
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699"/>
<Vtx Seq="2" Y="6.438" X="-2.699"/>
<Vtx Seq="3" Y="6.438" X="2.699"/>
<Vtx Seq="4" Y="2" X="2.699"/>
</Trampoline>
</BoatShape>
<BoatShape ShapeID="18">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.04"/>
<Vtx Seq="2" Y="0.11" X="-1.18"/>
<Vtx Seq="3" Y="0.42" X="-1.28"/>
<Vtx Seq="4" Y="3.74" X="-1.29"/>
<Vtx Seq="5" Y="5.36" X="-1.21"/>
<Vtx Seq="6" Y="6.29" X="-1.08"/>
<Vtx Seq="7" Y="7.15" X="-0.84"/>
<Vtx Seq="8" Y="7.63" X="-0.62"/>
<Vtx Seq="9" Y="7.94" X="-0.34"/>
<Vtx Seq="10" Y="8.06" X="0"/>
<Vtx Seq="11" Y="7.94" X="0.34"/>
<Vtx Seq="12" Y="7.63" X="0.62"/>
<Vtx Seq="13" Y="7.15" X="0.84"/>
<Vtx Seq="14" Y="6.29" X="1.08"/>
<Vtx Seq="15" Y="5.36" X="1.21"/>
<Vtx Seq="16" Y="3.74" X="1.29"/>
<Vtx Seq="17" Y="0.42" X="1.28"/>
<Vtx Seq="18" Y="0.11" X="1.18"/>
<Vtx Seq="19" Y="0" X="1.04"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="24">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.5"/>
<Vtx Seq="2" Y="7" X="-2.5"/>
<Vtx Seq="3" Y="12.6" X="-2.2"/>
<Vtx Seq="4" Y="12.6" X="2.2"/>
<Vtx Seq="5" Y="7" X="2.5"/>
<Vtx Seq="6" Y="0" X="2.5"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="34">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.16"/>
<Vtx Seq="2" Y="5.51" X="-1.16"/>
<Vtx Seq="3" Y="5.846" X="-0.84"/>
<Vtx Seq="4" Y="5.846" X="0.84"/>
<Vtx Seq="5" Y="5.51" X="1.16"/>
<Vtx Seq="6" Y="0" X="1.16"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="35">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.461"/>
<Vtx Seq="2" Y="6" X="-1.461"/>
<Vtx Seq="3" Y="7" X="-1.44"/>
<Vtx Seq="4" Y="8" X="-1.38"/>
<Vtx Seq="5" Y="9" X="-1.17"/>
<Vtx Seq="6" Y="10" X="-0.76"/>
<Vtx Seq="7" Y="10.6" X="-0.34"/>
<Vtx Seq="8" Y="10.61" X="0"/>
<Vtx Seq="9" Y="10.6" X="0.34"/>
<Vtx Seq="10" Y="10" X="0.76"/>
<Vtx Seq="11" Y="9" X="1.17"/>
<Vtx Seq="12" Y="8" X="1.38"/>
<Vtx Seq="13" Y="7" X="1.44"/>
<Vtx Seq="14" Y="6" X="1.461"/>
<Vtx Seq="15" Y="0" X="1.461"/>
</Vertices>
</BoatShape>
</BoatShapes>
<Boats>
<Boat Type="Yacht" SourceID="201" ShapeID="15" StoweName="USA" ShortName="ORACLE" ShorterName="USA" BoatName="ORACLE TEAM USA" HullNum="AC4515" Skipper="SPITHILL" Helmsman="SPITHILL" Country="USA" PeliID="101" RadioIP="172.20.2.101">
<GPSposition Z="1.78" Y="-0.331" X="-0.006"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="301" ShapeID="15" StoweName="SWE" ShortName="ARTEMIS" ShorterName="SWE" BoatName="ARTEMIS RACING" HullNum="AC4517" Skipper="OUTTERIDGE" Helmsman="OUTTERIDGE" Country="SWE" PeliID="102" RadioIP="172.20.2.102">
<GPSposition Z="1.727" Y="-0.359" X="-0.0121"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="401" ShapeID="15" StoweName="NZL" ShortName="ETNZ" ShorterName="NZL" BoatName="EMIRATES TEAM NZ" HullNum="AC4503" Skipper="ASHBY" Helmsman="BURLING" Country="NZL" PeliID="103" RadioIP="172.20.2.103">
<GPSposition Z="1.881" Y="-0.291" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="501" ShapeID="15" StoweName="JPN" ShortName="JAPAN" ShorterName="JPN" BoatName="SOFTBANK TEAM JAPAN" HullNum="AC4504" Skipper="BARKER" Helmsman="BARKER" Country="JPN" PeliID="104" RadioIP="172.20.2.104">
<GPSposition Z="1.805" Y="-0.322" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="601" ShapeID="15" StoweName="FRA" ShortName="FRANCE" ShorterName="FRA" BoatName="GROUPAMA TEAM FRANCE" HullNum="AC4505" Skipper="CAMMAS" Helmsman="CAMMAS" Country="FRA" PeliID="105" RadioIP="172.20.2.105">
<GPSposition Z="1.863" Y="-0.3" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="701" ShapeID="15" StoweName="GBR" ShortName="GBR" ShorterName="GBR" BoatName="LAND ROVER BAR" HullNum="AC4516" Skipper="ANSLIE" Helmsman="ANSLIE" Country="GBR" PeliID="106" RadioIP="172.20.2.106">
<GPSposition Z="1.734" Y="-0.352" X="0"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
</Boats>
</BoatConfig>
+15 -36
View File
@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?> <?import javafx.scene.control.ListView?>
@@ -11,6 +16,7 @@
<?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?> <?import javafx.scene.text.Text?>
<GridPane fx:id="lobbyScreen" nodeOrientation="LEFT_TO_RIGHT" prefHeight="960.0" prefWidth="1530.0" style="-fx-background-color: #2C2c36;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.visualiser.controllers.LobbyController"> <GridPane fx:id="lobbyScreen" nodeOrientation="LEFT_TO_RIGHT" prefHeight="960.0" prefWidth="1530.0" style="-fx-background-color: #2C2c36;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.visualiser.controllers.LobbyController">
<columnConstraints> <columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" /> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
@@ -53,41 +59,14 @@
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints> </rowConstraints>
<children> <children>
<ListView fx:id="firstListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1"> <TextArea fx:id="playerFourTxt" editable="false" maxHeight="211.0" maxWidth="175.0" minHeight="211.0" minWidth="175.0" prefHeight="211.0" prefWidth="175.0" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
<GridPane.margin> <TextArea fx:id="playerEightTxt" editable="false" maxHeight="211.0" maxWidth="175.0" minHeight="211.0" minWidth="175.0" prefHeight="211.0" prefWidth="175.0" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" /> <TextArea fx:id="playerSevenTxt" editable="false" maxHeight="211.0" maxWidth="175.0" minHeight="211.0" minWidth="175.0" prefHeight="211.0" prefWidth="175.0" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
</GridPane.margin> <TextArea fx:id="playerThreeTxt" editable="false" maxHeight="211.0" maxWidth="175.0" minHeight="211.0" minWidth="175.0" prefHeight="211.0" prefWidth="175.0" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
</ListView> <TextArea fx:id="playerSixTxt" editable="false" maxHeight="211.0" maxWidth="175.0" minHeight="211.0" minWidth="175.0" prefHeight="211.0" prefWidth="175.0" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<ListView fx:id="secondListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2"> <TextArea fx:id="playerFiveTxt" editable="false" maxHeight="211.0" maxWidth="175.0" minHeight="211.0" minWidth="175.0" prefHeight="211.0" prefWidth="175.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<GridPane.margin> <TextArea fx:id="playerOneTxt" editable="false" maxHeight="211.0" maxWidth="175.0" minHeight="211.0" minWidth="175.0" prefHeight="211.0" prefWidth="175.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" /> <TextArea fx:id="playerTwoTxt" editable="false" maxHeight="211.0" maxWidth="175.0" minHeight="211.0" minWidth="175.0" prefHeight="211.0" prefWidth="175.0" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
</GridPane.margin>
</ListView>
<ListView fx:id="thirdListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="3">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin></ListView>
<ListView fx:id="fourthListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="4">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin>
</ListView>
<ListView fx:id="eighthListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="4" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin></ListView>
<ListView fx:id="fifthListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin></ListView>
<ListView fx:id="sixthListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin></ListView>
<ListView fx:id="seventhListView" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="3" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="40.0" right="40.0" top="10.0" />
</GridPane.margin></ListView>
<ImageView fx:id="firstImageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM"> <ImageView fx:id="firstImageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<GridPane.margin> <GridPane.margin>
<Insets bottom="10.0" /> <Insets bottom="10.0" />
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
@@ -10,6 +15,7 @@
<?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?> <?import javafx.scene.text.Text?>
<AnchorPane fx:id="holder" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="960.0" prefWidth="1530.0" style="-fx-background-color: #2C2c36;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.visualiser.controllers.StartScreenController"> <AnchorPane fx:id="holder" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="960.0" prefWidth="1530.0" style="-fx-background-color: #2C2c36;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.visualiser.controllers.StartScreenController">
<children> <children>
<GridPane fx:id="startScreen2" layoutX="365.0" layoutY="285.0" nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" style="-fx-background-color: #2C2c36;"> <GridPane fx:id="startScreen2" layoutX="365.0" layoutY="285.0" nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" style="-fx-background-color: #2C2c36;">
@@ -25,7 +31,7 @@
<Insets left="5.0" right="5.0" /> <Insets left="5.0" right="5.0" />
</GridPane.margin> </GridPane.margin>
</Button> </Button>
<TextField fx:id="ipTextField" alignment="CENTER" maxWidth="-Infinity" prefHeight="25.0" prefWidth="148.0" promptText="Host IP" text="132.181.14." GridPane.halignment="RIGHT" GridPane.rowIndex="4"> <TextField fx:id="ipTextField" alignment="CENTER" maxWidth="-Infinity" prefHeight="25.0" prefWidth="148.0" promptText="Host IP" text="localhost" GridPane.halignment="RIGHT" GridPane.rowIndex="4">
<GridPane.margin> <GridPane.margin>
<Insets bottom="10.0" left="5.0" right="85.0" top="10.0" /> <Insets bottom="10.0" left="5.0" right="85.0" top="10.0" />
</GridPane.margin> </GridPane.margin>
+12
View File
@@ -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"
+3 -3
View File
@@ -9,9 +9,9 @@ public class ColorsTest {
@Test @Test
public void testNextColor() { public void testNextColor() {
Color expectedColors[] = {Color.RED, Color.PERU, Color.SEAGREEN, Color.GREEN, Color.BLUE, Color.PURPLE}; Color expectedColors[] = {Color.RED, Color.PERU, Color.GOLD, Color.GREEN, Color.BLUE,
for (int i = 0; i<6; i++) Color.PURPLE, Color.DEEPPINK, Color.GRAY};
{ for (int i = 0; i < 8; i++) {
Assert.assertEquals(expectedColors[i], Colors.getColor()); Assert.assertEquals(expectedColors[i], Colors.getColor());
} }
} }
-22
View File
@@ -1,22 +0,0 @@
package seng302;
import org.junit.Test;
public class TestRaceTimer {
@Test
public void testPositiveTimeString(){
// RaceViewController controller = new RaceViewController();
// String result = controller.convertTimeToMinutesSeconds(61);
//
// assertTrue(result.equals("01:01"));
}
@Test
public void testNegativeTimeString(){
// RaceViewController controller = new RaceViewController();
// String result = controller.convertTimeToMinutesSeconds(-61);
//
// assertTrue(result.equals("-01:01"));
}
}
@@ -0,0 +1,67 @@
package seng302.model;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import seng302.gameServer.GameState;
import seng302.utilities.GeoUtility;
/**
* Test update function in Yacht.java to make sure yacht will not be collide each other within 25.0
* meters.
*/
public class UpdateYachtTest {
private ServerYacht yacht1 = new ServerYacht("Yacht", 1, "1", "Yacht" + 1, "Yacht" + 1, "Test1");
private ServerYacht yacht2 = new ServerYacht("Yacht", 2, "2", "Yacht" + 2, "Yacht" + 2, "Test2");
private GeoPoint geoPoint1 = new GeoPoint(50.0, 50.0);
private GeoPoint geoPoint2 = GeoUtility.getGeoCoordinate(geoPoint1, 90.0, 50.0);
@Before
public void setUpRace() {
new GameState("");
GameState.addYacht(1, yacht1);
GameState.addYacht(2, yacht2);
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
}
@Test
public void testUpdateYachtWithCollision() {
// Yacht 1 heading towards 90 degrees heading
yacht1.setLocation(geoPoint1);
// Yacht 2 heading towards 270 degrees heading
yacht2.setLocation(geoPoint1);
// Start yacht 1 and rest yacht 2
if (!yacht1.getSailIn()) {
yacht1.toggleSailIn();
}
GameState.checkForCollision(yacht1);
double moved = GeoUtility.getDistance(yacht1.getLocation(), geoPoint1);
Assert.assertEquals(GameState.BOUNCE_DISTANCE_YACHT, moved, 0.1);
}
@Test
public void testUpdateYachtWithoutCollision() {
// Yacht 1 heading towards 90 degrees heading
yacht1.setLocation(geoPoint1);
// Yacht 2 heading towards 270 degrees heading
yacht2.setLocation(geoPoint2);
// Start yacht 1 and rest yacht 2
if (!yacht1.getSailIn()) {
yacht1.toggleSailIn();
}
GameState.checkForCollision(yacht1);
Assert.assertTrue(
GameState.YACHT_COLLISION_DISTANCE < GeoUtility.getDistance(geoPoint1, geoPoint2
)
); //Check that yachts are actually far enough apart for no collision.
Assert.assertEquals(geoPoint1.getLat(), yacht1.getLocation().getLat(), 0.001);
Assert.assertEquals(geoPoint1.getLng(), yacht1.getLocation().getLng(), 0.001);
Assert.assertEquals(geoPoint2.getLat(), yacht1.getLocation().getLat(), 0.001);
Assert.assertEquals(geoPoint2.getLng(), yacht1.getLocation().getLng(), 0.001);
}
}
@@ -1,54 +0,0 @@
package seng302.model;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
/**
* Use this link to test geo distances
* http://www.csgnetwork.com/gpsdistcalc.html
* Created by wmu16 on 3/08/17.
*/
public class YachtTest {
private Yacht yacht;
private CompoundMark compoundMark;
private Double toleranceRatio = 0.01;
private GeoPoint p1 = new GeoPoint(57.670333, 11.827833);
private GeoPoint p2 = new GeoPoint(57.671524, 11.844495);
private GeoPoint p3 = new GeoPoint(57.670822, 11.843392);
private GeoPoint p4 = new GeoPoint(25.694829, 98.392049);
@Before
public void setup() {
yacht = new Yacht("Yacht",
0,
"0",
"WillIsCool",
"HaomingIsOk",
"NZL");
yacht.setLocation(57.670333, 11.827833);
compoundMark = new CompoundMark(0, "HaomingsMark");
Mark subMark1 = new Mark("H", 57.671524, 11.844495, 0);
Mark subMark2 = new Mark("H", 57.670822, 11.843392, 0);
compoundMark.addSubMarks(subMark1, subMark2);
yacht.setNextMark(compoundMark);
}
@Test
public void testDistanceToNextMark() {
Double actual, expected;
actual = yacht.calcDistanceToNextMark();
expected = 927d;
assertEquals(expected, actual, expected * toleranceRatio);
}
}
@@ -0,0 +1,68 @@
package seng302.model.mark;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import seng302.model.GeoPoint;
/**
* A class to test the compound mark calss
* Created by wmu16 on 10/08/17.
*/
public class CompoundMarkTest {
private Mark mark1;
private Mark mark2;
private CompoundMark gateMark;
private CompoundMark singleMark;
private static Double TOLERANCE_RATIO = 0.01;
@Before
public void setUp() throws Exception {
mark1 = new Mark("Mark1", 1, 57.670333, 11.842833, 0);
mark2 = new Mark("Mark2", 2, 57.671524, 11.844495, 1);
List<Mark> gateMarks = new ArrayList<Mark>();
gateMarks.add(mark1);
gateMarks.add(mark2);
List<Mark> singleMarks = new ArrayList<Mark>();
singleMarks.add(mark1);
gateMark = new CompoundMark(0, "Fun Mark", gateMarks);
singleMark = new CompoundMark(1, "Awesome Mark", singleMarks);
}
@Test
public void getSubMark() throws Exception {
assertEquals(mark1, gateMark.getSubMark(1));
assertEquals(mark2, gateMark.getSubMark(2));
assertEquals(mark1, singleMark.getSubMark(1));
}
@Test
public void getMidPoint() throws Exception {
GeoPoint result = gateMark.getMidPoint();
assertEquals(57.6709285, result.getLat(), result.getLat() * TOLERANCE_RATIO);
assertEquals(11.843664, result.getLng(), result.getLng() * TOLERANCE_RATIO);
result = singleMark.getMidPoint();
assertEquals(result, mark1);
}
@Test
public void isGate() throws Exception {
assertTrue(gateMark.isGate());
assertFalse(singleMark.isGate());
}
}
+30 -43
View File
@@ -1,21 +1,23 @@
package seng302.models; package seng302.models;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import seng302.model.mark.Mark; import seng302.model.mark.CompoundMark;
import seng302.model.mark.MarkOrder; import seng302.model.mark.MarkOrder;
import seng302.model.mark.RacePosition;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
public class MarkOrderTest { public class MarkOrderTest {
private static MarkOrder markOrder; private static MarkOrder markOrder;
private static Integer currentSeqID;
@BeforeClass @BeforeClass
public static void setup(){ public static void setup(){
markOrder = new MarkOrder(); markOrder = new MarkOrder();
currentSeqID = 0;
} }
/** /**
@@ -26,54 +28,39 @@ public class MarkOrderTest {
assertTrue(markOrder != null); assertTrue(markOrder != null);
} }
/**
* Test if .getNextMark() returns null if it is called with the final mark in the race
*/
@Test @Test
public void testNextMarkAtEnd(){ public void testIsLastMark() {
// There are no marks in the XML, therefore this can't be tested currentSeqID = 0;
if (markOrder.getMarkOrder().size() == 0){ assertFalse(markOrder.isLastMark(currentSeqID));
return;
}
Mark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); currentSeqID = markOrder.getMarkOrder().size() - 1;
Integer lastIndex = markOrder.getMarkOrder().size() - 1; assertTrue(markOrder.isLastMark(currentSeqID));
RacePosition lastRacePosition = new RacePosition(lastIndex, lastMark, null);
assertEquals(null, markOrder.getNextPosition(lastRacePosition).getNextMark());
} }
/**
* Test if .getNextMark() method returns the next mark in the race
*/
@Test @Test
public void testNextMark(){ public void testGetNextMark() {
// There are not enough marks for this to be tested currentSeqID = 4;
if (markOrder.getMarkOrder().size() < 2){ CompoundMark nextMark = markOrder.getMarkOrder().get(4 + 1);
return; assertEquals(nextMark, markOrder.getNextMark(currentSeqID));
}
RacePosition firstRacePos = new RacePosition(0, markOrder.getMarkOrder().get(0), null); currentSeqID = 3;
nextMark = markOrder.getMarkOrder().get(3 + 1);
assertEquals(markOrder.getMarkOrder().get(1).getName(), markOrder.getNextPosition(firstRacePos).getNextMark().getName()); assertEquals(nextMark, markOrder.getNextMark(currentSeqID));
} }
/**
* Test if a whole race can be completed
*/
@Test @Test
public void testMarkSequence(){ public void testGetCurrentMark() {
RacePosition current = markOrder.getFirstPosition(); currentSeqID = 0;
CompoundMark currentMark = markOrder.getMarkOrder().get(0);
assertEquals(currentMark, markOrder.getCurrentMark(0));
}
while (!current.getIsFinishingLeg()){ @Test
public void testGetPreviousMark() {
current = markOrder.getNextPosition(current); currentSeqID = 1;
CompoundMark prevMark = markOrder.getMarkOrder().get(0);
if (current.getIsFinishingLeg()){ assertEquals(prevMark, markOrder.getPreviousMark(currentSeqID));
assertEquals(null, current.getNextMark());
}
}
} }
@AfterClass @AfterClass
@@ -0,0 +1,87 @@
package seng302.models;
import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import seng302.gameServer.GameState;
import seng302.model.PolarTable;
import seng302.model.ServerYacht;
public class YachtTest {
private static ServerYacht y1;
//Yacht y2;
private static Double windDirection = 180d;
private static Double windSpeed = 20d;
private static GameState gs;
@BeforeClass
public static void setUp() {
y1 = new ServerYacht("Yacht", 101, "Y1", "Y1", "Yacht 1", "C1");
gs = new GameState("localhost");
}
@Test
public void tackGybeTest() {
HashMap<Double, Double> values = new HashMap<>();
values.put(280.0, 80.0);
values.put(270.0, 90.0);
values.put(359.0, 1.0);
values.put(180.0, 180.0);
values.put(75.0, 285.0);
for (Double begin : values.keySet()) {
y1.setHeading(begin);
y1.tackGybe(windDirection);
for (int i = 0; i < 200; i++) {
y1.runAutoPilot();
}
assertEquals(values.get(begin), y1.getHeading(), 5.0);
}
}
@Test
public void vmgTest() {
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
Double upwind = PolarTable.getOptimalUpwindVMG(windSpeed).keySet().iterator().next();
Double downwind = PolarTable.getOptimalDownwindVMG(windSpeed).keySet().iterator().next();
HashMap<Double, Double> values = new HashMap<>();
upwind = (double) Math.floorMod(upwind.longValue() + windDirection.longValue(), 360L);
Double upwindRight = upwind;
Double upwindLeft = 360 - upwindRight;
downwind = (double) Math.floorMod(downwind.longValue() + windDirection.longValue(), 360L);
Double downwindRight = downwind;
Double downwindLeft = 360 - downwindRight;
values.put(190d, upwindRight);
values.put(170d, upwindLeft);
values.put(10d, downwindLeft);
values.put(350d, downwindRight);
for (Double begin : values.keySet()) {
y1.setHeading(begin);
y1.turnToVMG();
for (int i = 0; i < 200; i++) {
y1.runAutoPilot();
}
y1.disableAutoPilot();
assertEquals(values.get(begin), y1.getHeading(), 5.0);
}
}
@AfterClass
public static void tearDown() {
y1 = null;
}
}
@@ -6,12 +6,11 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import seng302.model.GeoPoint; import seng302.model.GeoPoint;
import seng302.utilities.GeoUtility;
/** /**
* http://www.geoplaner.com/ For plotting geo points for visualisation
* To test methods in GeoUtility. * To test methods in GeoUtility.
* Use this site to calculate distances * Use this site to calculate distances
* https://rechneronline.de/geo-coordinates/#distance * https://rechneronline.de/geo-coordinates/#distance
@@ -150,4 +149,44 @@ public class GeoUtilityTest {
assertFalse(GeoUtility.isPointInTriangle(v1, v2, v3, p2)); assertFalse(GeoUtility.isPointInTriangle(v1, v2, v3, p2));
} }
@Test
public void testCheckCrossedGate() {
GeoPoint mark1 = new GeoPoint(37.40937, -122.62233);
GeoPoint mark2 = new GeoPoint(37.40938, -122.62154);
GeoPoint location1 = new GeoPoint(37.40964, -122.62196);
GeoPoint location2 = new GeoPoint(37.40910, -122.62189);
GeoPoint location3 = new GeoPoint(37.40949, -122.62202);
GeoPoint location4 = new GeoPoint(37.40927, -122.62152);
// M1 -> M3 enters from CCW side
assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location1, location2) == 2);
// M1 -> M3 doesn't across
assertFalse(GeoUtility.checkCrossedLine(mark1, mark2, location1, location3) > 0);
// M2 -> M3 enters from CW side
assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location2, location3) == 1);
// order changes intersect direction
assertTrue(GeoUtility.checkCrossedLine(mark2, mark1, location2, location3) == 2);
assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location3, location2) == 2);
}
@Test
public void testDirtyMiddlePoint() {
GeoPoint result = GeoUtility.getDirtyMidPoint(p1, p2);
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);
}
} }
@@ -0,0 +1,75 @@
package seng302.visualiser.ClientToServerTests;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import seng302.gameServer.MainServerThread;
import seng302.visualiser.ClientToServerThread;
/**
* Test for checking how regularly packets are sent from ClientToServer Thread.
*/
public class RegularPacketsTest {
private MainServerThread serverThread;
private ClientToServerThread clientThread;
@Before
public void setup() throws Exception {
// new GameState("localhost");
// serverThread = new MainServerThread();
// clientThread = new ClientToServerThread("localhost", 4942);
// GameState.setCurrentStage(GameStages.RACING);
}
@Test
public void packetsSentAtRegularIntervals () throws Exception {
// final double TEST_DISTANCE = 10.0;
// serverThread.startGame();
// SleepThreadMaxDelay();
// ServerYacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0);
// double startAngle = yacht.getHeading();
// long startTime = System.currentTimeMillis();
// clientThread.sendBoatAction(BoatAction.UPWIND);
// Thread.sleep(200);
// while (Math.abs(yacht.getHeading() - startAngle) < TEST_DISTANCE) {
// Thread.sleep(1);
// }
// clientThread.sendBoatAction(BoatAction.MAINTAIN_HEADING);
// long endTime = System.currentTimeMillis();
// SleepThreadMaxDelay();
// //Allowed to be two loops of delay due to loop delay and processing delay at client + server ends.
// Assert.assertEquals(
// TEST_DISTANCE / ServerYacht.TURN_STEP * ClientToServerThread.PACKET_SENDING_INTERVAL_MS,
// (endTime - startTime), 2 * ClientToServerThread.PACKET_SENDING_INTERVAL_MS);
}
// @Test
// public void testArbitraryPacketSent() throws Exception {
// serverThread.startGame();
// SleepThreadMaxDelay();
// Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0);
// boolean startState = yacht.getSailIn();
// clientThread.sendBoatAction(BoatAction.SAILS_IN);
// SleepThreadMaxDelay();
// Assert.assertEquals(startState, !yacht.getSailIn());
// }
/**
* Give time for processing and packet sending. 200ms listed as absolute maximum for an
* acceptable delay.
* @throws Exception Thrown if thread crashes or something
*/
private void SleepThreadMaxDelay() throws Exception {
Thread.sleep(200);
}
@After
public void teardown () throws Exception {
// clientThread.setSocketToClose();
// serverThread.terminate();
// GameState.setCurrentStage(GameStages.LOBBYING);
// for (int i = 0; i<20; i++)
// SleepThreadMaxDelay(); //Make sure socket is closed and toolkit remade.
}
}
@@ -1,4 +1,4 @@
package seng302.visualizer.annotations; package seng302.visualiser.annotations;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -0,0 +1,29 @@
package seng302.visualiser.map;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import seng302.model.ClientYacht;
/**
* Created by kre39 on 6/08/17.
*/
public class BoatSailAnimationToggleTest {
private ClientYacht yacht;
@Before
public void setup() throws Exception{
yacht = new ClientYacht("Yacht", 1, "YACHT", "YAC", "Test Yacht", "NZ");
}
@Test
public void sailToggleTest() throws Exception {
assertFalse(yacht.getSailIn());
yacht.toggleSail();
assertTrue(yacht.getSailIn());
}
}
+53
View File
@@ -0,0 +1,53 @@
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.ServerYacht;
import seng302.visualiser.ClientToServerThread;
/**
* Created by kre39 on 7/08/17.
*/
public class ToggleSailSteps {
MainServerThread mst;
ClientToServerThread client;
long startTime;
@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
ServerYacht 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
ServerYacht yacht = (new ArrayList<>(GameState.getYachts().values())).get(0);
if (arg1 == "in") {
Assert.assertTrue(yacht.getSailIn());
} else {
Assert.assertFalse(yacht.getSailIn());
}
}
}