Merge branch 'develop' into story1118_map_arrows

# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/model/RaceState.java
#	src/main/java/seng302/visualiser/GameClient.java
This commit is contained in:
Calum
2017-08-16 20:34:27 +12:00
19 changed files with 442 additions and 206 deletions
+97 -29
View File
@@ -6,16 +6,19 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatStatus;
import seng302.gameServer.messages.MarkRoundingMessage;
import seng302.gameServer.messages.MarkType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RoundingBoatStatus;
import seng302.gameServer.messages.YachtEventCodeMessage;
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.Limit;
import seng302.model.Player;
import seng302.model.PolarTable;
import seng302.model.ServerYacht;
@@ -23,6 +26,7 @@ import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkOrder;
import seng302.utilities.GeoUtility;
import seng302.utilities.XMLParser;
/**
* A Static class to hold information about the current state of the game (model)
@@ -33,6 +37,7 @@ public class GameState implements Runnable {
@FunctionalInterface
interface NewMessageListener {
void notify(Message message);
}
@@ -59,6 +64,7 @@ public class GameState implements Runnable {
private static MarkOrder markOrder;
private static long startTime;
private static Set<Mark> marks;
private static List<Limit> courseLimit;
private static List<NewMessageListener> markListeners;
@@ -81,7 +87,7 @@ public class GameState implements Runnable {
yachts = new HashMap<>();
players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress;
;
currentStage = GameStages.LOBBYING;
isRaceStarted = false;
//set this when game stage changes to prerace
@@ -89,9 +95,27 @@ public class GameState implements Runnable {
markOrder = new MarkOrder(); //This could be instantiated at some point with a select map?
markListeners = new ArrayList<>();
new Thread(this).start(); //Run the auto updates on the game state
resetStartTime();
new Thread(this, "GameState").start(); //Run the auto updates on the game state
marks = new MarkOrder().getAllMarks();
setCourseLimit("/server_config/race.xml");
}
private void setCourseLimit(String url) {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder;
Document document = null;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
document = documentBuilder.parse(new InputSource(getClass().getResourceAsStream(url)));
} catch (Exception e) {
// sorry, we have to catch general one, otherwise we have to catch five different exceptions.
logger.trace("Failed to load course limit for boundary collision detection.", e);
}
courseLimit = XMLParser.parseRace(document).getCourseLimit();
}
public static String getHostIpAddress() {
@@ -135,10 +159,6 @@ public class GameState implements Runnable {
}
public static void setCurrentStage(GameStages currentStage) {
if (currentStage == GameStages.RACING) {
startTime = System.currentTimeMillis();
}
GameState.currentStage = currentStage;
}
@@ -150,6 +170,10 @@ public class GameState implements Runnable {
return startTime;
}
public static void resetStartTime(){
startTime = System.currentTimeMillis() + MainServerThread.TIME_TILL_START;
}
public static Double getWindDirection() {
return windDirection;
}
@@ -184,13 +208,13 @@ public class GameState implements Runnable {
@Override
public void run() {
while (true) {
while (currentStage != GameStages.FINISHED) {
try {
Thread.sleep(1000 / STATE_UPDATES_PER_SECOND);
} catch (InterruptedException e) {
System.out.println("[GameState] interrupted exception");
}
if (currentStage == GameStages.PRE_RACE) {
if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) {
update();
}
@@ -229,22 +253,51 @@ public class GameState implements Runnable {
* Called periodically in this GameState thread to update the GameState values
*/
public void update() {
Boolean raceFinished = true;
Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0;
previousUpdateTime = System.currentTimeMillis();
if (System.currentTimeMillis() > startTime) {
GameState.setCurrentStage(GameStages.RACING);
}
for (ServerYacht yacht : yachts.values()) {
updateVelocity(yacht);
yacht.runAutoPilot();
yacht.updateLocation(timeInterval);
if (!yacht.getFinishedRace()) {
checkForCollision(yacht);
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
checkCollision(yacht);
checkForLegProgression(yacht);
}
raceFinished = false;
}
}
if (raceFinished) {
currentStage = GameStages.FINISHED;
}
}
public static void checkForCollision(ServerYacht serverYacht) {
ServerYacht collidedYacht = checkCollision(serverYacht);
/**
* Check if the yacht has crossed the course limit
*
* @param yacht the yacht to be tested
* @return a boolean value of if there is a boundary collision
*/
private static Boolean checkBoundaryCollision(ServerYacht yacht) {
for (int i = 0; i < courseLimit.size() - 1; i++) {
if (GeoUtility.checkCrossedLine(courseLimit.get(i), courseLimit.get(i + 1),
yacht.getLastLocation(), yacht.getLocation()) != 0) {
return true;
}
}
if (GeoUtility.checkCrossedLine(courseLimit.get(courseLimit.size() - 1), courseLimit.get(0),
yacht.getLastLocation(), yacht.getLocation()) != 0) {
return true;
}
return false;
}
public static void checkCollision(ServerYacht serverYacht) {
ServerYacht collidedYacht = checkYachtCollision(serverYacht);
if (collidedYacht != null) {
GeoPoint originalLocation = serverYacht.getLocation();
serverYacht.setLocation(
@@ -263,7 +316,7 @@ public class GameState implements Runnable {
new YachtEventCodeMessage(serverYacht.getSourceId())
);
} else {
Mark collidedMark = markCollidedWith(serverYacht);
Mark collidedMark = checkMarkCollision(serverYacht);
if (collidedMark != null) {
serverYacht.setLocation(
calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK)
@@ -274,6 +327,17 @@ public class GameState implements Runnable {
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId())
);
} else if (checkBoundaryCollision(serverYacht)) {
serverYacht.setLocation(
calculateBounceBack(serverYacht, serverYacht.getLocation(),
BOUNCE_DISTANCE_YACHT)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId())
);
}
}
}
@@ -289,7 +353,7 @@ public class GameState implements Runnable {
if (velocity < maxBoatSpeed - 500) {
yacht.changeVelocity(maxBoatSpeed / 100);
} else if (velocity > maxBoatSpeed + 500) {
yacht.changeVelocity(-maxBoatSpeed / 100);
yacht.changeVelocity(-velocity / 200);
} else {
yacht.setCurrentVelocity(maxBoatSpeed);
}
@@ -340,11 +404,13 @@ public class GameState implements Runnable {
/**
* 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);
// System.out.println(yacht.getCurrentMarkSeqID());
Boolean hasProgressed;
if (currentMarkSeqID == 0) {
@@ -493,7 +559,6 @@ public class GameState implements Runnable {
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;
}
@@ -503,7 +568,7 @@ public class GameState implements Runnable {
}
private static Mark markCollidedWith(ServerYacht yacht) {
private static Mark checkMarkCollision(ServerYacht yacht) {
Set<Mark> marksInRace = GameState.getMarks();
for (Mark mark : marksInRace) {
if (GeoUtility.getDistance(yacht.getLocation(), mark)
@@ -519,12 +584,14 @@ public class GameState implements Runnable {
*
* @return The boats new position
*/
private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith, Double bounceDistance) {
Double heading = GeoUtility.getBearing(yacht.getLocation(), collidedWith);
private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith,
Double bounceDistance) {
Double heading = GeoUtility.getBearing(yacht.getLastLocation(), collidedWith);
// Invert heading
heading -= 180;
Integer newHeading = Math.floorMod(heading.intValue(), 360);
return GeoUtility.getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance);
return GeoUtility
.getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance);
}
/**
@@ -533,11 +600,12 @@ public class GameState implements Runnable {
*
* @return yacht to compare to all other yachts.
*/
private static ServerYacht checkCollision(ServerYacht yacht) {
private static ServerYacht checkYachtCollision(ServerYacht yacht) {
for (ServerYacht otherYacht : GameState.getYachts().values()) {
if (otherYacht != yacht) {
Double distance = GeoUtility.getDistance(otherYacht.getLocation(), yacht.getLocation());
Double distance = GeoUtility
.getDistance(otherYacht.getLocation(), yacht.getLocation());
if (distance < YACHT_COLLISION_DISTANCE) {
return otherYacht;
}
@@ -13,7 +13,7 @@ import seng302.gameServer.messages.Message;
* Will call .clientDisconnected on the delegate when a heartbeat message
* cannot be sent to a player
*/
public class HeartbeatThread extends Thread{
public class HeartbeatThread implements Runnable {
private final int HEARTBEAT_PERIOD = 200;
private ClientConnectionDelegate delegate;
private Integer seqNum;
@@ -23,6 +23,9 @@ public class HeartbeatThread extends Thread{
this.delegate = delegate;
seqNum = 0;
disconnectedPlayers = new Stack<>();
Thread thread = new Thread(this, "HeartBeat");
thread.start();
}
/**
@@ -1,12 +1,14 @@
package seng302.gameServer;
import gherkin.lexer.Fi;
import java.io.IOException;
import java.net.ServerSocket;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import seng302.gameServer.messages.Message;
import seng302.gameServer.server.messages.*;
import seng302.model.GeoPoint;
import seng302.model.Player;
import seng302.model.PolarTable;
@@ -24,6 +26,10 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
private static final int PORT = 4942;
private static final Integer CLIENT_UPDATES_PER_SECOND = 10;
private static final int LOG_LEVEL = 1;
private static final int WARNING_TIME = 10 * -1000;
private static final int PREPATORY_TIME = 5 * -1000;
public static final int TIME_TILL_START = 10 * 1000;
private boolean terminated;
private Thread thread;
@@ -43,20 +49,15 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
GameState.addMarkPassListener(this::broadcastMessage);
terminated = false;
thread = new Thread(this);
thread = new Thread(this, "MainServer");
thread.start();
}
public void run() {
ServerListenThread serverListenThread;
HeartbeatThread heartbeatThread;
serverListenThread = new ServerListenThread(serverSocket, this);
heartbeatThread = new HeartbeatThread(this);
heartbeatThread.start();
serverListenThread.start();
new HeartbeatThread(this);
new ServerListenThread(serverSocket, this);
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
while (!terminated) {
@@ -77,7 +78,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
//FINISHED
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
terminate();
}
}
@@ -159,14 +160,60 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
t.schedule(new TimerTask() {
@Override
public void run() {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.sendRaceStatusMessage();
broadcastMessage(makeRaceStatusMessage());
if (GameState.getCurrentStage() == GameStages.PRE_RACE || GameState.getCurrentStage() == GameStages.LOBBYING) {
broadcastMessage(makeRaceStartMessage());
}
}
}, 0, 500);
}
private RaceStartStatusMessage makeRaceStartMessage() {
Long raceStartTime = GameState.getStartTime();
return new RaceStartStatusMessage(1, raceStartTime ,
1, RaceStartNotificationType.SET_RACE_START_TIME);
}
private RaceStatusMessage makeRaceStatusMessage() {
// variables taken from GameServerThread
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
RaceStatus raceStatus;
for (Player player : GameState.getPlayers()) {
ServerYacht y = player.getYacht();
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(), 0,
0, 0, 1234L,
1234L);
boatSubMessages.add(m);
}
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
raceStatus = RaceStatus.PRESTART;
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
raceStatus = RaceStatus.PRESTART;
if (timeTillStart > WARNING_TIME) {
raceStatus = RaceStatus.WARNING;
}
if (timeTillStart > PREPATORY_TIME) {
raceStatus = RaceStatus.PREPARATORY;
}
} else {
raceStatus = RaceStatus.STARTED;
}
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
GameState.getWindDirection(),
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
RaceType.MATCH_RACE, 1, boatSubMessages);
}
public void terminate() {
terminated = true;
}
@@ -8,13 +8,16 @@ import java.net.Socket;
* A class for a thread to listen to connections
* Created by wmu16 on 11/07/17.
*/
public class ServerListenThread extends Thread{
public class ServerListenThread implements Runnable {
private ServerSocket serverSocket;
private ClientConnectionDelegate delegate;
public ServerListenThread(ServerSocket serverSocket, ClientConnectionDelegate delegate){
this.serverSocket = serverSocket;
this.delegate = delegate;
Thread thread = new Thread(this, "ServerListen");
thread.start();
}
/**
@@ -88,7 +88,7 @@ public class ServerToClientThread implements Runnable, Observer {
return;
}
thread = new Thread(this);
thread = new Thread(this, "ServerToClient");
thread.start();
}
@@ -209,8 +209,6 @@ public class ServerToClientThread implements Runnable, Observer {
}
}
} catch (Exception e) {
// TODO: 24/07/17 zyt10 - fix a logic here when a client disconnected
// serverLog("ERROR OCCURRED, CLOSING SERVER CONNECTION: " + socket.getRemoteSocketAddress().toString(), 1);
closeSocket();
return;
}
@@ -226,7 +224,7 @@ public class ServerToClientThread implements Runnable, Observer {
}
//@TODO calculate lat/lng values
xml.setRegatta(new Regatta("RaceVision Test Game", 57.6679590, 11.8503233));
xml.setRegatta(new Regatta("Party Parrot Test Server", "Bermuda Test Course", 57.6679590, 11.8503233));
xml.setRace(race);
XMLMessage xmlMessage;
@@ -318,31 +316,6 @@ public class ServerToClientThread implements Runnable, Observer {
return thread;
}
public void sendRaceStatusMessage() {
// variables taken from GameServerThread
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
RaceStatus raceStatus;
for (Player player : GameState.getPlayers()) {
ServerYacht y = player.getYacht();
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(), 0,
0, 0, 1234L,
1234L);
boatSubMessages.add(m);
}
if (GameState.getCurrentStage() == GameStages.RACING) {
raceStatus = RaceStatus.STARTED;
} else {
raceStatus = RaceStatus.WARNING;
}
sendMessage(new RaceStatusMessage(1, raceStatus, GameState.getStartTime(), GameState.getWindDirection(),
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
RaceType.MATCH_RACE, 1, boatSubMessages));
}
public Socket getSocket() {
return socket;
}
+1 -1
View File
@@ -47,7 +47,7 @@ public class ClientYacht extends Observable {
private String country;
private Long estimateTimeAtFinish;
private Boolean sailIn = false;
private Boolean sailIn = true;
private Integer currentMarkSeqID = 0;
private Long markRoundTime;
private Long timeTillNext;
+14 -13
View File
@@ -24,10 +24,11 @@ public class RaceState {
// private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
private double windSpeed;
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
private long raceTime;
private double windDirection;
private long serverSystemTime;
private long expectedStartTime;
private boolean isRaceStarted = false;
long timeTillStart;
private List<ClientYacht> collisions = new ArrayList<>();
private List<CollisionListener> collisionListeners = new ArrayList<>();
@@ -36,8 +37,8 @@ public class RaceState {
public void updateState (RaceStatusData data) {
this.windSpeed = data.getWindSpeed();
this.windDirection.set(data.getWindDirection());
this.raceTime = data.getCurrentTime();
this.windDirection = data.getWindDirection();
this.serverSystemTime = data.getCurrentTime();
this.expectedStartTime = data.getExpectedStartTime();
this.isRaceStarted = data.isRaceStarted();
}
@@ -47,16 +48,20 @@ public class RaceState {
}
public void updateState (RaceStartData data) {
// this.timeTillStart = data.getRaceStartTime();
System.out.println(data.getRaceStartTime());
this.timeTillStart = data.getRaceStartTime();
}
public String getRaceTimeStr () {
return DATE_TIME_FORMAT.format(raceTime);
long raceTime = serverSystemTime - expectedStartTime;
if (raceTime < 0) {
return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000));
} else {
return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime);
}
}
public long getTimeTillStart () {
return (expectedStartTime - raceTime) / 1000;
return (expectedStartTime - serverSystemTime);
}
public double getWindSpeed() {
@@ -68,11 +73,7 @@ public class RaceState {
}
public long getRaceTime() {
return raceTime;
}
public long getExpectedStartTime() {
return expectedStartTime;
return serverSystemTime;
}
public boolean isRaceStarted () {
+3 -13
View File
@@ -42,12 +42,11 @@ public class ServerYacht extends Observable {
private Double autoHeading;
//Mark Rounding
private Integer currentMarkSeqID = 0;
private Integer currentMarkSeqID;
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,
@@ -61,15 +60,15 @@ public class ServerYacht extends Observable {
this.country = country;
this.sailIn = false;
this.isAuto = false;
this.location = new GeoPoint(57.670341, 11.826856);
this.location = new GeoPoint(57.67046, 11.83751);
this.lastLocation = location;
this.heading = 120.0; //In degrees
this.currentVelocity = 0d; //in mms-1
this.currentMarkSeqID = 0;
this.hasEnteredRoundingZone = false;
this.hasPassedLine = false;
this.hasPassedThroughGate = false;
this.finishedRace = false;
}
@@ -322,15 +321,6 @@ public class ServerYacht extends Observable {
return boatName;
}
public void setIsFinished(Boolean isFinished) {
finishedRace = isFinished;
}
public Boolean getFinishedRace() {
return finishedRace;
}
public Double getCurrentVelocity() {
return currentVelocity;
}
@@ -18,10 +18,10 @@ public class Regatta {
private Integer utcOffset;
private Double magneticVariation;
public Regatta(String name, Double latitude, Double longitude) {
public Regatta(String name, String courseName, Double latitude, Double longitude) {
this.name = name;
this.id = DEFAULT_REGATTA_ID;
this.courseName = name;
this.courseName = courseName;
this.latitude = latitude;
this.longitude = longitude;
@@ -12,6 +12,7 @@ import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import seng302.gameServer.GameState;
import seng302.gameServer.MainServerThread;
import seng302.gameServer.messages.BoatAction;
import seng302.model.ClientYacht;
@@ -46,6 +47,7 @@ public class GameClient {
private RegattaXMLData regattaData;
private RaceXMLData courseData;
private RaceState raceState = new RaceState();
private LobbyController lobbyController;
private ObservableList<String> clientLobbyList = FXCollections.observableArrayList();
@@ -75,8 +77,18 @@ public class GameClient {
LobbyController lobbyController = loadLobby();
lobbyController.setPlayerListSource(clientLobbyList);
lobbyController.disableReadyButton();
lobbyController.setTitle("Connected to host - IP : " + ipAddress + " Port : " + portNumber);
if (regattaData != null){
lobbyController.setTitle(regattaData.getRegattaName());
lobbyController.setCourseName(regattaData.getCourseName());
}
else{
lobbyController.setTitle(ipAddress);
lobbyController.setCourseName("");
}
lobbyController.addCloseListener((exitCause) -> this.loadStartScreen());
this.lobbyController = lobbyController;
}
/**
@@ -95,15 +107,27 @@ public class GameClient {
socketThread.addStreamObserver(this::parsePackets);
LobbyController lobbyController = loadLobby();
lobbyController.setPlayerListSource(clientLobbyList);
lobbyController.setTitle("Hosting Lobby - IP : " + ipAddress + " Port : " + portNumber);
if (regattaData != null){
lobbyController.setTitle("Hosting: " + regattaData.getRegattaName());
lobbyController.setCourseName(regattaData.getCourseName());
}
else{
lobbyController.setTitle("Hosting: " + ipAddress);
lobbyController.setCourseName("");
}
lobbyController.addCloseListener(exitCause -> {
if (exitCause == CloseStatus.READY) {
GameState.resetStartTime();
lobbyController.disableReadyButton();
server.startGame();
} else if (exitCause == CloseStatus.LEAVE) {
loadStartScreen();
}
});
this.lobbyController = lobbyController;
server.setGameClient(this);
}
@@ -175,13 +199,18 @@ public class GameClient {
switch (packet.getType()) {
case RACE_STATUS:
processRaceStatusUpdate(StreamParser.extractRaceStatus(packet));
if (raceState.getTimeTillStart() <= 5000) {
startRaceIfAllDataReceived();
}
break;
case REGATTA_XML:
regattaData = XMLParser.parseRegatta(
StreamParser.extractXmlMessage(packet)
);
raceState.setTimeZone(
TimeZone.getTimeZone(
ZoneId.ofOffset("UTC", ZoneOffset.ofHours(regattaData.getUtcOffset()))
@@ -210,6 +239,7 @@ public class GameClient {
case RACE_START_STATUS:
raceState.updateState(StreamParser.extractRaceStartStatus(packet));
if (lobbyController != null) lobbyController.updateRaceState(raceState);
break;
case BOAT_LOCATION:
@@ -294,7 +324,8 @@ public class GameClient {
raceFinished = false;
}
}
if (raceFinished) {
if (raceFinished == true) {
close();
loadFinishScreenView();
}
@@ -340,14 +371,15 @@ public class GameClient {
socketThread.sendBoatAction(BoatAction.TACK_GYBE); break;
//TODO Allow a zoom in and zoom out methods
case Z: // zoom in
System.out.println("Zoom in");
raceView.getGameView().zoomIn();
break;
case X: // zoom out
System.out.println("Zoom out");
raceView.getGameView().zoomOut();
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)
+48 -18
View File
@@ -73,6 +73,7 @@ public class GameView extends Pane {
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
private Map<ClientYacht, AnnotationBox> annotations = new HashMap<>();
private ObservableList<Node> gameObjects;
private BoatObject selectedBoat = null;
private Group annotationsGroup = new Group();
private Group wakesGroup = new Group();
private Group boatObjectGroup = new Group();
@@ -96,18 +97,18 @@ public class GameView extends Pane {
double scaleFactor = 1;
public void zoomOut() {
scaleFactor = 0.95;
for (Node child : getChildren()) {
child.setScaleX(child.getScaleX() * scaleFactor);
child.setScaleY(child.getScaleY() * scaleFactor);
scaleFactor = 0.1;
if (this.getScaleX() > 0.5) {
this.setScaleX(this.getScaleX() - scaleFactor);
this.setScaleY(this.getScaleY() - scaleFactor);
}
}
public void zoomIn() {
scaleFactor = 1.05;
for (Node child : getChildren()) {
child.setScaleX(child.getScaleX() * scaleFactor);
child.setScaleY(child.getScaleY() * scaleFactor);
scaleFactor = 0.10;
if (this.getScaleX() < 2.5) {
this.setScaleX(this.getScaleX() + scaleFactor);
this.setScaleY(this.getScaleY() + scaleFactor);
}
}
@@ -116,14 +117,25 @@ public class GameView extends Pane {
VERTICAL
}
private void trackBoat() {
if (selectedBoat != null) {
double x = selectedBoat.getBoatLayoutX();
double y = selectedBoat.getBoatLayoutY();
double displacementX = this.getWidth();
double displacementY = this.getHeight();
this.setLayoutX((-x + (displacementX / 2.0)) * this.getScaleX());
this.setLayoutY((-y + (displacementY / 2.0)) * this.getScaleY());
} else {
this.setLayoutX(0);
this.setLayoutY(0);
}
}
public GameView () {
gameObjects = this.getChildren();
// create image view for map, bind panel size to image
gameObjects.add(mapImage);
fpsDisplay.setLayoutX(5);
fpsDisplay.setLayoutY(20);
fpsDisplay.setStrokeWidth(2);
gameObjects.add(fpsDisplay);
gameObjects.add(raceBorder);
gameObjects.add(markers);
initializeTimer();
@@ -141,6 +153,7 @@ public class GameView extends Pane {
@Override
public void handle(long now) {
trackBoat();
if (lastTime == 0) {
lastTime = now;
} else {
@@ -164,9 +177,7 @@ public class GameView extends Pane {
lastTime = now;
}
}
// Platform.runLater(() ->
boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation());
// );
}
};
}
@@ -449,6 +460,22 @@ public class GameView extends Pane {
// drawGoogleMap();
}
private void setSelectedBoat(BoatObject bo, Boolean isSelected) {
if (this.selectedBoat == bo && !isSelected) {
this.selectedBoat = null;
boatObjects.forEach((boat, group) ->
group.setIsSelected(false)
);
} else if (isSelected) {
this.selectedBoat = bo;
for (BoatObject group : boatObjects.values()) {
if (group != bo) {
group.setIsSelected(false);
}
}
}
}
/**
* Draws all the boats.
* @param yachts The yachts to set in the race
@@ -459,6 +486,7 @@ public class GameView extends Pane {
for (ClientYacht yacht : yachts) {
Paint colour = Colors.getColor();
newBoat = new BoatObject();
newBoat.addSelectedBoatListener(this::setSelectedBoat);
newBoat.setFill(colour);
boatObjects.put(yacht, newBoat);
createAndBindAnnotationBox(yacht, colour);
@@ -471,9 +499,6 @@ public class GameView extends Pane {
BoatObject bo = boatObjects.get(boat);
Point2D p2d = findScaledXY(lat, lon);
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
// annotations.get(boat).setLayoutX(p2d.getX());
// annotations.get(boat).setLayoutY(p2d.getY());
// annotations.get(boat).setLocation(100d, 100d);
annotations.get(boat).setLocation(p2d.getX(), p2d.getY());
bo.setTrajectory(
heading,
@@ -734,7 +759,7 @@ public class GameView extends Pane {
public void setBoatAsPlayer (ClientYacht playerYacht) {
this.playerYacht = playerYacht;
this.playerYacht.toggleSail();
playerYacht.toggleSail();
boatObjects.get(playerYacht).setAsPlayer();
CompoundMark currentMark = course.get(playerYacht.getLegNumber());
System.out.println("currentMark = " + currentMark);
@@ -812,4 +837,9 @@ public class GameView extends Pane {
timeline.setOnFinished(event -> Platform.runLater(() -> gameObjects.remove(circle)));
timeline.play();
}
public void setFrameRateFXText(Text fpsDisplay) {
this.fpsDisplay = null;
this.fpsDisplay = fpsDisplay;
}
}
@@ -1,8 +1,7 @@
package seng302.visualiser.controllers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.*;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -14,6 +13,8 @@ import javafx.scene.image.ImageView;
import javafx.scene.text.Text;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
import seng302.model.RaceState;
import seng302.visualiser.GameClient;
/**
* A class describing the actions of the lobby screen
@@ -67,9 +68,14 @@ public class LobbyController {
private ImageView seventhImageView;
@FXML
private ImageView eighthImageView;
@FXML
private Text timeUntilStart;
@FXML
private Text courseNameText;
private List<ImageView> imageViews = new ArrayList<>();
private List<TextArea> listViews = new ArrayList<>();
private RaceState raceState;
private int MAX_NUM_PLAYERS = 8;
@@ -89,6 +95,8 @@ public class LobbyController {
fifthImageView, sixthImageView, seventhImageView, eighthImageView
);
initialiseImageView();
timeUntilStart.setText("Waiting For Host...");
}
/**
@@ -133,7 +141,9 @@ public class LobbyController {
@FXML
public void readyButtonPressed() {
GameState.setCurrentStage(GameStages.RACING);
GameState.setCurrentStage(GameStages.PRE_RACE);
// Do countdown logic here
for (LobbyCloseListener readyListener : lobbyListeners)
readyListener.notify(CloseStatus.READY);
}
@@ -142,6 +152,10 @@ public class LobbyController {
lobbyIpText.setText(title);
}
public void setCourseName(String courseName){
courseNameText.setText(courseName);
}
public void addCloseListener(LobbyCloseListener listener) {
lobbyListeners.add(listener);
}
@@ -154,6 +168,11 @@ public class LobbyController {
Platform.runLater(this::updatePlayers);
}
public void updateRaceState(RaceState raceState){
this.raceState = raceState;
timeUntilStart.setText("Starting in: " + raceState.getRaceTimeStr());
}
public void disableReadyButton () {
readyButton.setDisable(true);
readyButton.setVisible(false);
@@ -71,7 +71,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
private Button selectAnnotationBtn;
@FXML
private ComboBox<ClientYacht> yachtSelectionComboBox;
@FXML
private Text fpsDisplay;
//Race Data
private Map<Integer, ClientYacht> participants;
private Map<Integer, CompoundMark> markers;
@@ -115,7 +116,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
initialiseSparkLine();
gameView = new GameView();
Platform.runLater(() -> contentAnchorPane.getChildren().add(gameView));
gameView.setFrameRateFXText(fpsDisplay);
Platform.runLater(() -> contentAnchorPane.getChildren().add(0, gameView));
gameView.setBoats(new ArrayList<>(participants.values()));
gameView.updateBorder(raceData.getCourseLimit());
gameView.updateCourse(
@@ -370,12 +372,12 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* Updates the clock for the race
*/
private void updateRaceTime() {
if (!raceState.isRaceStarted()) {
timerLabel.setFill(Color.RED);
timerLabel.setText("Race Finished!");
} else {
// if (!raceState.isRaceStarted()) {
// timerLabel.setFill(Color.RED);
// timerLabel.setText("Race Finished!");
// } else {
timerLabel.setText(raceState.getRaceTimeStr());
}
// }
}
/**
@@ -95,7 +95,7 @@ public class AnnotationBox extends Group {
background.setStroke(theme);
background.setStrokeWidth(2);
background.setCache(true);
background.setCacheHint(CacheHint.SPEED);
background.setCacheHint(CacheHint.SCALE);
this.getChildren().add(background);
}
@@ -213,7 +213,7 @@ public class AnnotationBox extends Group {
Text text = new Text();
text.setFill(theme);
text.setStrokeWidth(2);
text.setCacheHint(CacheHint.SPEED);
// text.setCacheHint(CacheHint.QUALITY);
text.setCache(true);
return text;
}
@@ -1,9 +1,9 @@
package seng302.visualiser.fxObjects;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
@@ -24,6 +24,12 @@ import javafx.scene.transform.Rotate;
*/
public class BoatObject extends Group {
@FunctionalInterface
public interface SelectedBoatListener {
void notifySelected(BoatObject boatObject, Boolean isSelected);
}
//Constants for drawing
private static final double BOAT_HEIGHT = 15d;
private static final double BOAT_WIDTH = 10d;
@@ -42,9 +48,11 @@ public class BoatObject extends Group {
private double distanceTravelled, lastRotation;
private Point2D lastPoint;
private Paint colour = Color.BLACK;
private Boolean isSelected, destinationSet; //All boats are initialised as selected
private Boolean isSelected = false, destinationSet; //All boats are initialised as selected
private boolean isPlayer = false;
private List<SelectedBoatListener> selectedBoatListenerListeners = new ArrayList<>();
/**
* Creates a BoatGroup with the default triangular boat polygon.
*/
@@ -86,7 +94,7 @@ public class BoatObject extends Group {
});
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
boatPoly.setCache(true);
boatPoly.setCacheHint(CacheHint.SPEED);
// boatPoly.setCacheHint(CacheHint.SPEED);
// annotationBox = new AnnotationBox();
// annotationBox.setFill(colour);
@@ -288,6 +296,7 @@ public class BoatObject extends Group {
// }
public void setIsSelected(Boolean isSelected) {
updateListener(isSelected);
this.isSelected = isSelected;
setLineGroupVisible(isSelected);
setWakeVisible(isSelected);
@@ -367,6 +376,10 @@ public class BoatObject extends Group {
lastHeading = heading;
}
public Boolean getSelected() {
return isSelected;
}
public void setTrajectory(double heading, double velocity, double scaleFactorX, double scaleFactorY) {
// wake.setRotation(lastHeading - heading, velocity);
// rotateTo(heading);
@@ -374,4 +387,14 @@ public class BoatObject extends Group {
// yVelocity = Math.sin(Math.toRadians(heading)) * velocity * scaleFactorY;
lastHeading = heading;
}
private void updateListener(Boolean isSelected) {
for (SelectedBoatListener sbl : selectedBoatListenerListeners) {
sbl.notifySelected(this, isSelected);
}
}
public void addSelectedBoatListener(SelectedBoatListener sbl) {
selectedBoatListenerListeners.add(sbl);
}
}
+25 -5
View File
@@ -27,11 +27,6 @@
<RowConstraints maxHeight="100.0" minHeight="0.0" prefHeight="0.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Text fx:id="lobbyIpText" fill="WHITE" strokeType="OUTSIDE" strokeWidth="0.0" text="Lobby: IP" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER">
<font>
<Font size="29.0" />
</font>
</Text>
<GridPane prefHeight="166.0" prefWidth="1530.0" GridPane.rowIndex="2">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
@@ -109,5 +104,30 @@
</ImageView>
</children>
</GridPane>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="134.0" minHeight="10.0" prefHeight="32.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="134.0" minHeight="10.0" prefHeight="35.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="134.0" minHeight="10.0" prefHeight="32.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="79.0" minHeight="10.0" prefHeight="52.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="79.0" minHeight="10.0" prefHeight="35.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Text fx:id="lobbyIpText" fill="WHITE" strokeType="OUTSIDE" strokeWidth="0.0" text="Server Name" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="1">
<font>
<Font size="29.0" />
</font>
</Text>
<Text fx:id="timeUntilStart" fill="#f8f8f8" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="3">
<font>
<Font size="20.0" />
</font>
</Text>
<Text fx:id="courseNameText" fill="#e1e1e1" strokeType="OUTSIDE" strokeWidth="0.0" text="Course Name" GridPane.halignment="CENTER" GridPane.rowIndex="2" />
</children>
</GridPane>
</children>
</GridPane>
+34 -11
View File
@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.chart.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.chart.CategoryAxis?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
@@ -17,18 +23,38 @@
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<GridPane prefHeight="960.0" prefWidth="1530.0" 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.RaceViewController">
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="998.0" prefWidth="1530.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.visualiser.controllers.RaceViewController">
<children>
<AnchorPane layoutX="322.0" layoutY="130.0" prefHeight="998.0" prefWidth="1281.0"
style="-fx-background-color: skyblue;" AnchorPane.bottomAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<GridPane prefHeight="998.0" prefWidth="1281.0" AnchorPane.bottomAnchor="0.0"
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="630.0" minWidth="10.0"
prefWidth="68.0"/>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1213.0" minWidth="10.0"
prefWidth="1213.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="500.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints />
<RowConstraints />
<RowConstraints maxHeight="489.0" minHeight="1.0" prefHeight="24.0"
vgrow="SOMETIMES"/>
<RowConstraints maxHeight="997.0" minHeight="10.0" prefHeight="974.0"
vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<AnchorPane prefHeight="960.0" prefWidth="250.0" style="-fx-background-color: #2C2c36;" GridPane.rowSpan="3">
<AnchorPane fx:id="contentAnchorPane" prefHeight="200.0" prefWidth="200.0"
GridPane.columnSpan="2" GridPane.rowSpan="2"/>
<Text fx:id="fpsDisplay" strokeType="OUTSIDE" strokeWidth="0.0" text="60 FPS"
GridPane.halignment="CENTER" GridPane.valignment="CENTER"/>
</children>
</GridPane>
</children>
</AnchorPane>
<AnchorPane prefHeight="960.0" prefWidth="250.0" style="-fx-background-color: #2C2c36;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label layoutX="11.0" layoutY="283.0" text="Team Position" textFill="WHITE" />
<Label layoutX="13.0" layoutY="432.0" text="Settings" textFill="WHITE" />
@@ -76,8 +102,5 @@
</LineChart>
</children>
</AnchorPane>
<AnchorPane fx:id="contentAnchorPane" style="-fx-background-color: skyblue;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP">
</AnchorPane>
</children>
</GridPane>
</AnchorPane>
@@ -6,6 +6,8 @@ import org.junit.Test;
import seng302.gameServer.GameState;
import seng302.utilities.GeoUtility;
import static seng302.gameServer.GameState.checkCollision;
/**
* Test update function in Yacht.java to make sure yacht will not be collide each other within 25.0
* meters.
@@ -37,7 +39,7 @@ public class UpdateYachtTest {
if (!yacht1.getSailIn()) {
yacht1.toggleSailIn();
}
GameState.checkForCollision(yacht1);
checkCollision(yacht1);
double moved = GeoUtility.getDistance(yacht1.getLocation(), geoPoint1);
Assert.assertEquals(GameState.BOUNCE_DISTANCE_YACHT, moved, 0.1);
}
@@ -54,14 +56,14 @@ public class UpdateYachtTest {
if (!yacht1.getSailIn()) {
yacht1.toggleSailIn();
}
GameState.checkForCollision(yacht1);
checkCollision(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);
Assert.assertEquals(geoPoint1.getLat(), yacht1.getLocation().getLat(), 1.001);
Assert.assertEquals(geoPoint1.getLng(), yacht1.getLocation().getLng(), 1.001);
Assert.assertEquals(geoPoint2.getLat(), yacht1.getLocation().getLat(), 1.001);
Assert.assertEquals(geoPoint2.getLng(), yacht1.getLocation().getLng(), 1.001);
}
}
@@ -21,9 +21,9 @@ public class BoatSailAnimationToggleTest {
@Test
public void sailToggleTest() throws Exception {
assertFalse(yacht.getSailIn());
yacht.toggleSail();
assertTrue(yacht.getSailIn());
yacht.toggleSail();
assertFalse(yacht.getSailIn());
}
}