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
@@ -7,17 +7,28 @@ import java.io.OutputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javafx.application.Platform;
import javafx.scene.control.Alert;
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.ClientType;
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
@@ -25,6 +36,8 @@ import seng302.gameServer.server.messages.Message;
*/
public class ClientToServerThread implements Runnable {
/**
* Functional interface for receiving packets from client socket.
*/
@@ -47,9 +60,17 @@ public class ClientToServerThread implements Runnable {
private Socket socket;
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 ByteArrayOutputStream crcBuffer;
@@ -71,15 +92,8 @@ public class ClientToServerThread implements Runnable {
socket = new Socket(ipAddress, portNumber);
is = socket.getInputStream();
os = socket.getOutputStream();
Integer allocatedID = threeWayHandshake();
if (allocatedID != null) {
clientId = allocatedID;
clientLog("Successful handshake. Allocated ID: " + clientId, 1);
} else {
clientLog("Unsuccessful handshake", 1);
closeSocket();
return;
}
sendRegistrationRequest();
thread = new Thread(this);
thread.start();
@@ -128,15 +142,22 @@ public class ClientToServerThread implements Runnable {
if (streamPackets.size() > 0) {
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
} else {
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
for (ClientSocketListener csl : listeners)
csl.newPacket();
if (PacketType.RACE_REGISTRATION_RESPONSE == PacketType.assignPacketType(type, payload)){
processRegistrationResponse(new StreamPacket(type, payloadLength, timeStamp, payload));
}
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 {
clientLog("Packet has been dropped", 1);
}
}
} catch (ByteReadException e) {
e.printStackTrace();
closeSocket();
Platform.runLater(() -> {
Alert alert = new Alert(AlertType.ERROR);
@@ -147,7 +168,6 @@ public class ClientToServerThread implements Runnable {
clientLog(e.getMessage(), 1);
return;
}
// System.out.println("streamPackets = " + streamPackets.size());
}
closeSocket();
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
*
* @return the sourceID allocated to us by the server
* Sends a request to the server asking for a source ID
*/
private Integer threeWayHandshake() {
Integer ourSourceID = null;
while (true) {
try {
ourSourceID = is.read();
} catch (IOException e) {
clientLog("Three way handshake failed", 1);
}
if (ourSourceID != null) {
try {
os.write(ourSourceID);
return ourSourceID;
} catch (IOException e) {
clientLog("Three way handshake failed", 1);
return null;
}
}
private void sendRegistrationRequest() {
RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER);
try {
os.write(requestMessage.getBuffer());
} catch (IOException e) {
logger.error("Could not send registration request. Exiting");
System.exit(1);
}
}
/**
* Send the post-start race course information
* @param boatActionMessage The message to send
* Accepts a response to the registration request message, and updates the client OR quits
* @param packet The registration requests packet
*/
public void sendBoatActionMessage(BoatActionMessage boatActionMessage) {
try {
os.write(boatActionMessage.getBuffer());
} catch (IOException e) {
clientLog("Could not write to server", 1);
private void processRegistrationResponse(StreamPacket packet){
int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 3));
int statusCode = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 4,5));
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() {
try {
@@ -245,11 +349,8 @@ public class ClientToServerThread implements Runnable {
}
}
public Thread getThread() {
return thread;
}
public int getClientId () {
return clientId;
}
}
+122 -76
View File
@@ -13,17 +13,17 @@ import javafx.scene.Node;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import seng302.gameServer.MainServerThread;
import seng302.gameServer.server.messages.BoatAction;
import seng302.model.ClientYacht;
import seng302.model.RaceState;
import seng302.model.Yacht;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.parser.MarkRoundingData;
import seng302.model.stream.parser.PositionUpdateData;
import seng302.model.stream.parser.PositionUpdateData.DeviceType;
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.RegattaXMLData;
import seng302.gameServer.server.messages.BoatActionMessage;
import seng302.gameServer.server.messages.BoatActionType;
import seng302.utilities.StreamParser;
import seng302.utilities.XMLParser;
import seng302.visualiser.controllers.LobbyController;
@@ -31,7 +31,8 @@ import seng302.visualiser.controllers.LobbyController.CloseStatus;
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 {
@@ -41,20 +42,27 @@ public class GameClient {
private RaceViewController raceView;
private Map<Integer, Yacht> allBoatsMap;
private Map<Integer, ClientYacht> allBoatsMap;
private RegattaXMLData regattaData;
private RaceXMLData courseData;
private RaceState raceState = new RaceState();
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) {
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) {
try {
socketThread = new ClientToServerThread(ipAddress, portNumber);
@@ -62,6 +70,7 @@ public class GameClient {
ioe.printStackTrace();
System.out.println("Unable to connect to host...");
}
socketThread.addStreamObserver(this::parsePackets);
LobbyController lobbyController = loadLobby();
lobbyController.setPlayerListSource(clientLobbyList);
@@ -70,6 +79,11 @@ public class GameClient {
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) {
server = new MainServerThread();
try {
@@ -89,14 +103,14 @@ public class GameClient {
loadStartScreen();
}
});
server.setGameClient(this);
}
private void loadStartScreen() {
socketThread.setSocketToClose();
socketThread = null;
if (server != null) {
// TODO: 26/07/17 cir27 - handle disconnecting
// server.shutDown();
server.terminate();
server = null;
}
FXMLLoader fxmlLoader = new FXMLLoader(
@@ -115,7 +129,8 @@ public class GameClient {
* @return the lobby controller.
*/
private LobbyController loadLobby() {
FXMLLoader fxmlLoader = new FXMLLoader(GameClient.class.getResource("/views/LobbyView.fxml"));
FXMLLoader fxmlLoader = new FXMLLoader(
GameClient.class.getResource("/views/LobbyView.fxml"));
try {
holderPane.getChildren().clear();
holderPane.getChildren().add(fxmlLoader.load());
@@ -140,10 +155,24 @@ public class GameClient {
holderPane.getScene().setOnKeyPressed(this::keyPressed);
holderPane.getScene().setOnKeyReleased(this::keyReleased);
raceView = fxmlLoader.getController();
Yacht player = allBoatsMap.get(socketThread.getClientId());
ClientYacht player = allBoatsMap.get(socketThread.getClientId());
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() {
while (socketThread.getPacketQueue().peek() != null) {
StreamPacket packet = socketThread.getPacketQueue().poll();
@@ -174,17 +203,13 @@ public class GameClient {
break;
case BOAT_XML:
System.out.println("GOT SUM BOATS YAY :)");
allBoatsMap = XMLParser.parseBoats(
StreamParser.extractXmlMessage(packet)
);
clientLobbyList.clear();
allBoatsMap.forEach((id, boat) -> {
clientLobbyList.add(id + " " + boat.getBoatName());
// System.out.println(id + " " + boat.getBoatName());
});
// startRaceIfAllDataReceived();
allBoatsMap.forEach((id, boat) ->
clientLobbyList.add(id + " " + boat.getBoatName())
);
break;
case RACE_START_STATUS:
@@ -198,13 +223,18 @@ public class GameClient {
case MARK_ROUNDING:
updateMarkRounding(StreamParser.extractMarkRounding(packet));
break;
case YACHT_EVENT_CODE:
showCollisionAlert(StreamParser.extractYachtEventCode(packet));
break;
}
}
}
private void startRaceIfAllDataReceived() {
if (allXMLReceived() && raceView == null)
if (allXMLReceived() && raceView == null) {
loadRaceView();
}
}
private boolean allXMLReceived() {
@@ -217,8 +247,8 @@ public class GameClient {
private void updatePosition(PositionUpdateData positionData) {
if (positionData.getType() == DeviceType.YACHT_TYPE) {
if (allXMLReceived() && allBoatsMap.containsKey(positionData.getDeviceId())) {
Yacht yacht = allBoatsMap.get(positionData.getDeviceId());
yacht.updateLocation(positionData.getLat(),
ClientYacht clientYacht = allBoatsMap.get(positionData.getDeviceId());
clientYacht.updateLocation(positionData.getLat(),
positionData.getLon(), positionData.getHeading(),
positionData.getGroundSpeed());
}
@@ -235,11 +265,11 @@ public class GameClient {
*/
private void updateMarkRounding(MarkRoundingData roundingData) {
if (allXMLReceived()) {
Yacht yacht = allBoatsMap.get(roundingData.getBoatId());
yacht.setMarkRoundingTime(roundingData.getTimeStamp());
yacht.updateTimeSinceLastMarkProperty(
ClientYacht clientYacht = allBoatsMap.get(roundingData.getBoatId());
clientYacht.setMarkRoundingTime(roundingData.getTimeStamp());
clientYacht.updateTimeSinceLastMarkProperty(
raceState.getRaceTime() - roundingData.getTimeStamp());
yacht.setLastMarkRounded(
clientYacht.setLastMarkRounded(
courseData.getCompoundMarks().get(
roundingData.getMarkId()
)
@@ -250,21 +280,34 @@ public class GameClient {
private void processRaceStatusUpdate(RaceStatusData data) {
if (allXMLReceived()) {
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()) {
Yacht yacht = allBoatsMap.get((int) boatData[0]);
yacht.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]);
yacht.setEstimateTimeAtFinish(boatData[2]);
ClientYacht clientYacht = allBoatsMap.get((int) boatData[0]);
clientYacht.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]);
clientYacht.setEstimateTimeAtFinish(boatData[2]);
int legNumber = (int) boatData[3];
yacht.setLegNumber(legNumber);
yacht.setBoatStatus((int) boatData[4]);
if (legNumber != yacht.getLegNumber()) {
clientYacht.setLegNumber(legNumber);
clientYacht.setBoatStatus((int) boatData[4]);
if (legNumber != clientYacht.getLegNumber()) {
int placing = 1;
for (Yacht otherYacht : allBoatsMap.values()) {
if (otherYacht.getSourceId() != boatData[0] &&
yacht.getLegNumber() <= otherYacht.getLegNumber())
for (ClientYacht otherClientYacht : allBoatsMap.values()) {
if (otherClientYacht.getSourceId() != boatData[0] &&
clientYacht.getLegNumber() <= otherClientYacht.getLegNumber())
placing++;
}
yacht.setPositionInteger(placing);
clientYacht.setPositionInteger(placing);
}
}
}
@@ -279,47 +322,50 @@ public class GameClient {
* Handle the key-pressed event from the text field.
* @param e The key event triggering this call
*/
public 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) {
private void keyPressed(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
BoatActionMessage boatActionMessage = new BoatActionMessage(
BoatActionType.SAILS_IN);
socketThread.sendBoatActionMessage(boatActionMessage);
case SPACE: // align with vmg
socketThread.sendBoatAction(BoatAction.VMG); break;
case PAGE_UP: // upwind
socketThread.sendBoatAction(BoatAction.UPWIND); break;
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;
}
}
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 javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
@@ -19,12 +22,14 @@ import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
import seng302.model.ClientYacht;
import javafx.util.Duration;
import seng302.model.Colors;
import seng302.model.GeoPoint;
import seng302.model.Limit;
import seng302.model.Yacht;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
@@ -38,10 +43,10 @@ import seng302.visualiser.map.CanvasMap;
*/
public class GameView extends Pane {
private double bufferSize = 50;
private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias.
private double panelHeight = 960;
private double canvasWidth = 1100;
private double bufferSize = 50;
private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias.
private double panelHeight = 960;
private double canvasWidth = 1100;
private double canvasHeight = 920;
private boolean horizontalInversion = false;
@@ -51,6 +56,8 @@ public class GameView extends Pane {
private double referencePointX, referencePointY;
private double metersPerPixelX, metersPerPixelY;
final double SCALE_DELTA = 1.1;
private Text fpsDisplay = new Text();
private Polygon raceBorder = new CourseBoundary();
@@ -59,8 +66,8 @@ public class GameView extends Pane {
private List<Limit> borderPoints;
private Map<Mark, Marker> markerObjects;
private Map<Yacht, BoatObject> boatObjects = new HashMap<>();
private Map<Yacht, AnnotationBox> annotations = new HashMap<>();
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
private Map<ClientYacht, AnnotationBox> annotations = new HashMap<>();
private ObservableList<Node> gameObjects;
private Group annotationsGroup = new Group();
private Group wakesGroup = new Group();
@@ -78,13 +85,33 @@ public class GameView extends Pane {
private Double frameRate = 60.0;
private int frameTimeIndex = 0;
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 {
HORIZONTAL,
VERTICAL
}
public GameView () {
public GameView() {
gameObjects = this.getChildren();
// create image view for map, bind panel size to image
gameObjects.add(mapImage);
@@ -97,7 +124,7 @@ public class GameView extends Pane {
initializeTimer();
}
private void initializeTimer () {
private void initializeTimer() {
Arrays.fill(frameTimes, 1_000_000_000 / 60);
timer = new AnimationTimer() {
private long lastTime = 0;
@@ -133,15 +160,15 @@ public class GameView extends Pane {
}
}
// 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
* map from google to display on image view. - Haoming 22/5/2017
* First find the top right and bottom left points' geo locations, then retrieve map from google
* to display on image view. - Haoming 22/5/2017
*/
private void drawGoogleMap() {
findMetersPerPixel();
@@ -199,13 +226,23 @@ public class GameView extends Pane {
for (Mark mark : cMark.getMarks()) {
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
if (cMark.isGate()) {
for (int i = 1; i < cMark.getMarks().size(); i++) {
gates.add(
makeAndBindGate(
markerObjects.get(cMark.getSubMark(i)),
markerObjects.get(cMark.getSubMark(i+1)),
markerObjects.get(cMark.getSubMark(i + 1)),
colour
)
);
@@ -269,7 +306,7 @@ public class GameView extends Pane {
gate.endYProperty().bind(
m2.layoutYProperty()
);
return gate;
return gate;
}
/**
@@ -307,26 +344,26 @@ public class GameView extends Pane {
/**
* 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;
final List<Group> wakes = new ArrayList<>();
for (Yacht yacht : yachts) {
for (ClientYacht clientYacht : clientYachts) {
Paint colour = Colors.getColor();
newBoat = new BoatObject();
newBoat.setFill(colour);
boatObjects.put(yacht, newBoat);
createAndBindAnnotationBox(yacht, colour);
boatObjects.put(clientYacht, newBoat);
createAndBindAnnotationBox(clientYacht, colour);
// wakesGroup.getChildren().add(newBoat.getWake());
wakes.add(newBoat.getWake());
boatObjectGroup.getChildren().add(newBoat);
trails.getChildren().add(newBoat.getTrail());
// 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);
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).setLayoutY(p2d.getY());
// 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();
newAnnotation.setFill(colour);
newAnnotation.addAnnotation(
"name", "Player: " + yacht.getShortName()
"name", "Player: " + clientYacht.getShortName()
);
// newAnnotation.addAnnotation(
// "velocity",
@@ -374,28 +411,28 @@ public class GameView extends Pane {
// 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))));
}
/**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point
* with the leftmost point, rightmost point, southern most point and northern most point
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with
* the leftmost point, rightmost point, southern most point and northern most point
* respectively.
*/
private void findMinMaxPoint(List<GeoPoint> points) {
List<GeoPoint> sortedPoints = new ArrayList<>(points);
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat));
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());
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::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());
if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) {
horizontalInversion = true;
@@ -415,15 +452,19 @@ public class GameView extends Pane {
if (scaleDirection == ScaleDirection.HORIZONTAL) {
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));
referencePointY = canvasHeight - (bufferSize + bufferSize);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility.getDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY = canvasHeight - (bufferSize + bufferSize);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += bufferSize;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility.getDistance(referencePoint, maxLatPoint);
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
} else {
referencePointY = canvasHeight - bufferSize;
referenceAngle = Math.abs(
@@ -431,11 +472,14 @@ public class GameView extends Pane {
GeoUtility.getDistance(referencePoint, minLonPoint)
)
);
referencePointX = bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility.getDistance(referencePoint, minLonPoint);
referencePointX += ((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
referencePointX = bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
}
if(horizontalInversion) {
if (horizontalInversion) {
referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize);
}
}
@@ -448,12 +492,12 @@ public class GameView extends Pane {
private double scaleRaceExtremities() {
double vertAngle = Math.abs(
GeoUtility.getBearingRad(minLatPoint, maxLatPoint)
GeoUtility.getBearingRad(minLatPoint, maxLatPoint)
);
double vertDistance =
Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint);
double horiAngle = Math.abs(
GeoUtility.getBearingRad(minLonPoint, maxLonPoint)
GeoUtility.getBearingRad(minLonPoint, maxLonPoint)
);
if (horiAngle <= (Math.PI / 2)) {
horiAngle = (Math.PI / 2) - horiAngle;
@@ -479,40 +523,45 @@ public class GameView extends Pane {
return findScaledXY(unscaled.getLat(), unscaled.getLng());
}
private Point2D findScaledXY (double unscaledLat, double unscaledLon) {
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xAxisLocation = referencePointX;
double yAxisLocation = referencePointY;
angleFromReference = GeoUtility.getBearingRad(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
// System.out.println("distanceFromReference = " + distanceFromReference);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
xAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
xAxisLocation += Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
xAxisLocation -= Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
xAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
}
if(horizontalInversion) {
if (horizontalInversion) {
xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize);
}
// System.out.println("yAxisLocation = " + yAxisLocation + " " + unscaledLat);
// System.out.println("xAxisLocation = " + xAxisLocation + " " + unscaledLon);
return new Point2D(xAxisLocation, yAxisLocation);
}
@@ -537,7 +586,7 @@ public class GameView extends Pane {
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) {
for (BoatObject boatObject : boatObjects.values()) {
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);
}
public void selectBoat (Yacht selectedYacht) {
public void selectBoat(ClientYacht selectedClientYacht) {
boatObjects.forEach((boat, group) ->
group.setIsSelected(boat == selectedYacht)
group.setIsSelected(boat == selectedClientYacht)
);
}
public void pauseRace () {
public void pauseRace() {
timer.stop();
}
public void startRace () {
public void setWindDir(double windDir) {
this.windDir = windDir;
}
public void startRace() {
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();
annotations.get(playerYacht).addAnnotation(
"velocity",
@@ -582,4 +643,40 @@ public class GameView extends Pane {
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.GridPane;
import javafx.scene.layout.Pane;
import seng302.model.Yacht;
import seng302.model.ClientYacht;
public class FinishScreenViewController implements Initializable {
@FXML
private GridPane finishScreenGridPane;
@FXML
private TableView<Yacht> finishOrderTable;
private TableView<ClientYacht> finishOrderTable;
@FXML
private TableColumn<Yacht, String> posCol;
private TableColumn<ClientYacht, String> posCol;
@FXML
private TableColumn<Yacht, String> boatNameCol;
private TableColumn<ClientYacht, String> boatNameCol;
@FXML
private TableColumn<Yacht, String> shortNameCol;
private TableColumn<ClientYacht, String> shortNameCol;
@FXML
private TableColumn<Yacht, String> countryCol;
private TableColumn<ClientYacht, String> countryCol;
ObservableList<Yacht> data = FXCollections.observableArrayList();
ObservableList<ClientYacht> data = FXCollections.observableArrayList();
@Override
public void initialize(URL location, ResourceBundle resources) {
@@ -61,9 +61,9 @@ public class FinishScreenViewController implements Initializable {
finishOrderTable.refresh();
}
public void setFinishers (List<Yacht> participants) {
List<Yacht> sorted = new ArrayList<>(participants);
sorted.sort(Comparator.comparingInt(Yacht::getPositionInteger));
public void setFinishers(List<ClientYacht> participants) {
List<ClientYacht> sorted = new ArrayList<>(participants);
sorted.sort(Comparator.comparingInt(ClientYacht::getPositionInteger));
finishOrderTable.getItems().setAll(sorted);
}
@@ -4,15 +4,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
@@ -33,28 +31,26 @@ public class LobbyController {
void notify(CloseStatus exitCause);
}
@FXML
private GridPane lobbyScreen;
@FXML
private Text lobbyIpText;
@FXML
private Button readyButton;
@FXML
private ListView<String> firstListView;
private TextArea playerOneTxt;
@FXML
private ListView secondListView;
private TextArea playerTwoTxt;
@FXML
private ListView thirdListView;
private TextArea playerThreeTxt;
@FXML
private ListView fourthListView;
private TextArea playerFourTxt;
@FXML
private ListView fifthListView;
private TextArea playerFiveTxt;
@FXML
private ListView sixthListView;
private TextArea playerSixTxt;
@FXML
private ListView seventhListView;
private TextArea playerSevenTxt;
@FXML
private ListView eighthListView;
private TextArea playerEightTxt;
@FXML
private ImageView firstImageView;
@FXML
@@ -72,79 +68,67 @@ public class LobbyController {
@FXML
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<ListView> listViews;
private List<TextArea> listViews = new ArrayList<>();
private int MAX_NUM_PLAYERS = 8;
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() {
imageViews = new ArrayList<>();
Collections
.addAll(imageViews, firstImageView, secondImageView, thirdImageView, fourthImageView,
fifthImageView, sixthImageView, seventhImageView, eighthImageView);
listViews = new ArrayList<>();
Collections.addAll(listViews, firstListView, secondListView, thirdListView, fourthListView, fifthListView,
sixthListView, seventhListView, eighthListView);
competitors = new ArrayList<>();
Collections.addAll(competitors, firstCompetitor, secondCompetitor, thirdCompetitor,
fourthCompetitor, fifthCompetitor, sixthCompetitor, seventhCompetitor, eighthCompetitor);
Collections.addAll(listViews,
playerOneTxt, playerTwoTxt, playerThreeTxt, playerFourTxt, playerFiveTxt, playerSixTxt,
playerSevenTxt, playerEightTxt
);
Collections.addAll(imageViews,
firstImageView, secondImageView, thirdImageView, fourthImageView,
fifthImageView, sixthImageView, seventhImageView, eighthImageView
);
initialiseImageView();
}
private void initialiseListView() {
listViews.forEach(listView -> listView.getItems().clear());
imageViews.forEach(gif -> gif.setVisible(false));
competitors.forEach(ol -> ol.removeAll());
/**
* Updates player names.
*/
private void updatePlayers() {
//Update players if one added.
for (int i = 0; i < players.size(); i++) {
competitors.get(i).add(players.get(i));
listViews.get(i).setItems(competitors.get(i));
listViews.get(i).setText(players.get(i));
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() {
imageViews.add(firstImageView);
imageViews.add(secondImageView);
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(
for (ImageView viewer : imageViews) {
viewer.setImage(
new Image(
RaceViewController.class.getResourceAsStream(
"/pics/sail.png")
)
);
viewer.setVisible(false);
}
}
@FXML
public void leaveLobbyButtonPressed() {
// TODO: 10/07/17 wmu16 - Finish function!
// setContentPane("/views/StartScreenView.fxml");
GameState.setCurrentStage(GameStages.CANCELLED);
// TODO: 20/07/17 wmu16 - Implement some way of terminating the game
// ClientState.setConnectedToHost(false);
for (LobbyCloseListener readyListener : lobbyListeners)
readyListener.notify(CloseStatus.LEAVE);
}
@FXML
@@ -154,32 +138,6 @@ public class LobbyController {
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) {
lobbyIpText.setText(title);
}
@@ -191,12 +149,13 @@ public class LobbyController {
public void setPlayerListSource (ObservableList<String> players) {
this.players = players;
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 () {
readyButton.setDisable(true);
readyButton.setVisible(false);
}
}
@@ -34,8 +34,8 @@ import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.StringConverter;
import seng302.model.ClientYacht;
import seng302.model.RaceState;
import seng302.model.Yacht;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.stream.xml.parser.RaceXMLData;
@@ -70,16 +70,15 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
@FXML
private Button selectAnnotationBtn;
@FXML
private ComboBox<Yacht> yachtSelectionComboBox;
private ComboBox<ClientYacht> yachtSelectionComboBox;
//Race Data
private Map<Integer, Yacht> participants;
private Map<Integer, ClientYacht> participants;
private Map<Integer, CompoundMark> markers;
private RaceXMLData courseData;
private GameView gameView;
private RaceState raceState;
private Timeline timerTimeline;
private Timer timer = new Timer();
private List<Series<String, Double>> sparkLineData = new ArrayList<>();
private ImportantAnnotationsState importantAnnotations;
@@ -101,7 +100,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}
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.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.
// Collect the racing yachts that aren't already in the chart
sparkLineData.clear();
List<Yacht> sparkLineCandidates = new ArrayList<>(participants.values());
List<ClientYacht> sparkLineCandidates = new ArrayList<>(participants.values());
// Create a new data series for new yachts
sparkLineCandidates
.stream()
@@ -228,29 +229,30 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
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)
sparkLineData.sort((o1, o2) -> {
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
if (leg2 < leg1){
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size() - 1).getXValue());
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size() - 1).getXValue());
if (leg2 < leg1) {
return 1;
} else {
return -1;
}
});
// Adds the new data series to the sparkline (and set the colour of the series)
Platform.runLater(() -> {
Platform.runLater(() ->
sparkLineData
.stream()
.filter(spark -> !raceSparkLine.getData().contains(spark))
.forEach(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() {
@@ -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
* @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
*/
void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){
void updateYachtPositionSparkline(ClientYacht clientYacht, Integer legNumber) {
for (XYChart.Series<String, Double> positionData : sparkLineData) {
positionData.getData().add(
new Data<>(
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
*
* @param yachtId id of yacht passed in to get the yachts colour
* @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();
if (color == null){
return String.format("#%02X%02X%02X",255,255,255);
if (color == null) {
return String.format("#%02X%02X%02X", 255, 255, 255);
}
return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ),
(int)( color.getBlue() * 255 )
return String.format("#%02X%02X%02X",
(int) (color.getRed() * 255),
(int) (color.getGreen() * 255),
(int) (color.getBlue() * 255)
);
}
/**
* 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.
* Updates of each of these attributes are called ONCE EACH SECOND
* orderings etc.. which are dependent on the info from the stream parser constantly. Updates of
* each of these attributes are called ONCE EACH SECOND
*/
private void initializeUpdateTimer() {
timer.scheduleAtFixedRate(new TimerTask() {
@@ -312,15 +315,16 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
updateRaceTime();
updateWindDirection();
updateOrder();
updateSparkLine();
// updateSparkLine();
}
}, 0, 1000);
}
/**
* Iterates over all corners until ones SeqID matches with the yachts current leg number.
* Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark
* Returns null if no next mark found.
* Iterates over all corners until ones SeqID matches with the yachts current leg number. Then
* it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark Returns
* null if no next mark found.
*
* @param bg The BoatGroup to find the next mark of
* @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());
// list of racing yacht id
List<Yacht> sorted = new ArrayList<>(participants.values());
sorted.sort(Comparator.comparingInt(Yacht::getPositionInteger));
List<ClientYacht> sorted = new ArrayList<>(participants.values());
sorted.sort(Comparator.comparingInt(ClientYacht::getPositionInteger));
List<Text> vboxEntries = new ArrayList<>();
for (Yacht yacht : sorted) {
for (ClientYacht clientYacht : sorted) {
// System.out.println("yacht == null " + String.valueOf(yacht == null));
if (yacht.getBoatStatus() == 3) { // 3 is finish status
Text textToAdd = new Text(yacht.getPositionInteger() + ". " +
yacht.getShortName() + " (Finished)");
if (clientYacht.getBoatStatus() == 3) { // 3 is finish status
Text textToAdd = new Text(clientYacht.getPositionInteger() + ". " +
clientYacht.getShortName() + " (Finished)");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
vboxEntries.add(textToAdd);
} else {
Text textToAdd = new Text(yacht.getPositionInteger() + ". " +
yacht.getShortName() + " ");
Text textToAdd = new Text(clientYacht.getPositionInteger() + ". " +
clientYacht.getShortName() + " ");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
textToAdd.setStyle("");
vboxEntries.add(textToAdd);
}
// System.out.println("finished a loop :))))))))))))");
}
Platform.runLater(() ->
positionVbox.getChildren().setAll(vboxEntries)
@@ -475,15 +478,17 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}
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 newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(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 newY = ref.getY() + (ref.getX() + distance - ref.getX()) * Math.sin(angle)
+ (ref.getY() + distance - ref.getY()) * Math.cos(angle);
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);
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
* for the combobox to take action upon selection
* Initialised the combo box with any yachts currently in the race and adds the required
* listener for the combobox to take action upon selection
*/
private void initialiseBoatSelectionComboBox() {
yachtSelectionComboBox.setItems(
@@ -540,7 +545,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
TimeUnit.MILLISECONDS.toHours(milliseconds),
TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60, //Modulus 60 minutes per hour
TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60 //Modulus 60 seconds per minute
);
);
}
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
*
* @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()) {
// //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.
@@ -591,8 +596,23 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
// }
}
public void updateRaceData (RaceXMLData raceData) {
public void updateRaceData(RaceXMLData raceData) {
this.courseData = raceData;
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
public void hostButtonPressed() {
new GameState(getLocalHostIp());
// new GameState(getLocalHostIp());
gameClient = new GameClient(holder);
gameClient.runAsHost(getLocalHostIp(), 4942);
// try {
@@ -30,9 +30,11 @@ public class BoatObject extends Group {
private double xVelocity;
private double yVelocity;
private double lastHeading;
private double sailState;
//Graphical objects
private Polyline trail = new Polyline();
private Polygon boatPoly;
private Polygon sail;
private Wake wake;
private Line leftLayLine;
private Line rightLayline;
@@ -94,7 +96,16 @@ public class BoatObject extends Group {
trail.setCache(true);
wake = new Wake(0, -BOAT_HEIGHT);
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) {
@@ -105,19 +116,30 @@ public class BoatObject extends Group {
/**
* 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 rotation The rotation by which the boat moves
* @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 dy = Math.abs(boatPoly.getLayoutY() - y);
Platform.runLater(() -> {
rotateTo(rotation);
rotateTo(rotation, sailIn, windDir);
boatPoly.setLayoutX(x);
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.setLayoutY(y);
});
@@ -142,8 +164,65 @@ public class BoatObject extends Group {
}
}
private void rotateTo(double rotation) {
boatPoly.getTransforms().setAll(new Rotate(rotation));
private Double normalizeHeading(double heading, double windDirection) {
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() {
@@ -268,18 +347,19 @@ public class BoatObject extends Group {
*/
public void setAsPlayer() {
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,
BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75
);
boatPoly.setStroke(Color.BLACK);
boatPoly.setStrokeWidth(3);
isPlayer = true;
animateSail();
}
public void setTrajectory(double heading, double velocity) {
public void setTrajectory(double heading, double velocity, double windDir) {
wake.setRotation(lastHeading - heading, velocity);
rotateTo(heading);
rotateTo(heading, false, windDir);
xVelocity = Math.cos(Math.toRadians(heading)) * velocity;
yVelocity = Math.sin(Math.toRadians(heading)) * velocity;
lastHeading = heading;
@@ -52,7 +52,7 @@ public class Wake extends Group {
arc.setFill(new Color(0.0, 0.0, 0.0, 0.0));
arcs[i] = arc;
arc.getTransforms().setAll(
new Rotate(1)
new Rotate(1)
);
}
super.getChildren().addAll(arcs);
@@ -60,15 +60,15 @@ public class Wake extends Group {
void setRotation (double rotation, double velocity) {
// if (Math.abs(rotations[0] - rotation) > 20) {
Platform.runLater(() -> {
rotate(rotation);
double rad = (14 / numWakes) + velocity;
for (Arc arc : arcs) {
arc.setRadiusX(rad);
arc.setRadiusY(rad);
rad += (14 / numWakes) + (velocity / 2.5);
}
});
Platform.runLater(() -> {
rotate(rotation);
double rad = (14 / numWakes) + velocity;
for (Arc arc : arcs) {
arc.setRadiusX(rad);
arc.setRadiusY(rad);
rad += (14 / numWakes) + (velocity / 2.5);
}
});
// } else {
// rotations[0] = 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
* @return the projection Point2D (x, y) on planar
*/
static Point2D toMapPoint(GeoPoint geo) {
public static Point2D toMapPoint(GeoPoint geo) {
double x, y;
Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
x = (origin.getX() + geo.getLng() * pixelsPerLngDegree);