Merge branch 'master' into MapWindRotation

# Conflicts:
#	racevisionGame/src/main/java/mock/app/Event.java
#	racevisionGame/src/main/java/mock/model/MockRace.java
#	racevisionGame/src/main/java/visualiser/app/VisualiserInput.java
#	racevisionGame/src/main/java/visualiser/model/VisualiserRace.java
This commit is contained in:
Fan-Wu Yang
2017-08-14 03:54:02 +12:00
114 changed files with 7215 additions and 2441 deletions
@@ -0,0 +1,152 @@
package visualiser.Controllers;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import shared.model.Bearing;
import shared.model.Wind;
import visualiser.model.VisualiserRace;
/**
* Controller for the arrow.fxml view.
*/
public class ArrowController {
@FXML
private Pane compass;
@FXML
private StackPane arrowStackPane;
@FXML
private ImageView arrowImage;
@FXML
private Circle circle;
@FXML
private Label northLabel;
@FXML
private Label windLabel;
@FXML
private Label speedLabel;
/**
* This is the property our arrow control binds to.
*/
private Property<Wind> wind;
/**
* Constructor.
*/
public ArrowController() {
}
/**
* Sets which wind property the arrow control should bind to.
* @param wind The wind property to bind to.
*/
public void setWindProperty(Property<Wind> wind) {
this.wind = wind;
wind.addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
Platform.runLater(() -> updateWind(newValue));
}
});
}
/**
* Updates the control to use the new wind value.
* This updates the arrow direction (due to bearing), arrow length (due to speed), and label (due to speed).
* @param wind The wind value to use.
*/
private void updateWind(Wind wind) {
updateWindBearing(wind.getWindDirection());
updateWindSpeed(wind.getWindSpeed());
}
/**
* Updates the control to account for the new wind speed.
* This changes the length (height) of the wind arrow, and updates the speed label.
* @param speedKnots The new wind speed, in knots.
*/
private void updateWindSpeed(double speedKnots) {
updateWindArrowLength(speedKnots);
updateWindSpeedLabel(speedKnots);
}
/**
* Updates the length of the wind arrow according to the specified wind speed.
* @param speedKnots Wind speed, in knots.
*/
private void updateWindArrowLength(double speedKnots) {
//At 2 knots, the arrow reaches its minimum height, and at 30 knots it reaches its maximum height.
double minKnots = 2;
double maxKnots = 30;
double deltaKnots = maxKnots - minKnots;
double minHeight = 25;
double maxHeight = 75;
double deltaHeight = maxHeight - minHeight;
//Clamp speed.
if (speedKnots > maxKnots) {
speedKnots = maxKnots;
} else if (speedKnots < minKnots) {
speedKnots = minKnots;
}
//How far between the knots bounds is the current speed?
double currentDeltaKnots = speedKnots - minKnots;
double currentKnotsScalar = currentDeltaKnots / deltaKnots;
//Thus, how far between the pixel height bounds should the arrow height be?
double newHeight = minHeight + (currentKnotsScalar * deltaHeight);
arrowImage.setFitHeight(newHeight);
}
/**
* Updates the wind speed label according to the specified wind speed.
* @param speedKnots Wind speed, in knots.
*/
private void updateWindSpeedLabel(double speedKnots) {
speedLabel.setText(String.format("%.1fkn", speedKnots));
}
/**
* Updates the control to account for a new wind bearing.
* This rotates the arrow according to the bearing.
* @param bearing The bearing to use to rotate arrow.
*/
private void updateWindBearing(Bearing bearing) {
//We need to display wind-from, so add 180 degrees.
Bearing fromBearing = Bearing.fromDegrees(bearing.degrees() + 180d);
//Rotate the wind arrow.
arrowStackPane.setRotate(fromBearing.degrees());
}
}
@@ -5,7 +5,6 @@ import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.control.*;
@@ -24,10 +23,8 @@ import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import visualiser.model.*;
import java.awt.*;
import java.io.IOException;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.ResourceBundle;
/**
@@ -60,6 +57,11 @@ public class RaceController extends Controller {
*/
private Sparkline sparkline;
/**
* The arrow controller.
*/
@FXML private ArrowController arrowController;
/**
* Service for sending keystrokes to server
*/
@@ -67,8 +69,18 @@ public class RaceController extends Controller {
@FXML private GridPane canvasBase;
@FXML private Pane arrow;
@FXML private SplitPane race;
/**
* This is the root node of the arrow control.
*/
@FXML private Pane arrow;
/**
* This is the pane we place the actual arrow control inside of.
*/
@FXML private StackPane arrowPane;
@FXML private Label timer;
@FXML private Label FPS;
@@ -118,15 +130,15 @@ public class RaceController extends Controller {
//Fps display.
initialiseFps(this.visualiserRace);
//Need to add the included arrow pane to the arrowPane container.
initialiseArrow();
//Information table.
initialiseInfoTable(this.visualiserRace);
//Sparkline.
initialiseSparkline(this.visualiserRace);
//Arrow.
initialiseArrow(this.visualiserRace);
//Race canvas.
initialiseRaceCanvas(this.visualiserRace);
@@ -294,7 +306,7 @@ public class RaceController extends Controller {
private void initialiseRaceCanvas(VisualiserRace race) {
//Create canvas.
raceCanvas = new ResizableRaceCanvas(race, arrow.getChildren().get(0));
raceCanvas = new ResizableRaceCanvas(race);
//Set properties.
raceCanvas.setMouseTransparent(true);
@@ -367,10 +379,11 @@ public class RaceController extends Controller {
/**
* Adds the included arrow pane (see arrow.fxml) to the arrowPane (see race.fxml).
* Initialises the arrow controller with data from the race to observe.
* @param race The race to observe.
*/
private void initialiseArrow() {
arrowPane.getChildren().add(arrow);
private void initialiseArrow(VisualiserRace race) {
arrowController.setWindProperty(race.windProperty());
}
@@ -25,8 +25,8 @@ public class App extends Application {
/**
* Method that displays the visualiser, starting with the title screen.
* @param stage the stage to be displayed
* @throws Exception if something wrong with title screen
* @param stage the stage to be displayed.
* @throws Exception if something wrong with title screen.
*/
public void start(Stage stage) throws Exception {
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@@ -201,12 +201,12 @@ public class VisualiserInput implements Runnable {
//Heartbeat.
case HEARTBEAT: {
Heartbeat heartbeat = (Heartbeat) message;
HeartBeat heartBeat = (HeartBeat) message;
//Check that the heartbeat number is greater than the previous value, and then set the last heartbeat time.
if (heartbeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) {
if (heartBeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) {
lastHeartbeatTime = System.currentTimeMillis();
lastHeartbeatSequenceNum = heartbeat.getSequenceNumber();
lastHeartbeatSequenceNum = heartBeat.getSequenceNumber();
//System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum);
}
@@ -1,6 +1,7 @@
package visualiser.gameController;
import network.BinaryMessageEncoder;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
@@ -12,6 +13,8 @@ import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Basic service for sending key presses to game server
@@ -47,22 +50,27 @@ public class ControllerClient {
* @throws IOException if socket write fails
*/
public void sendKey(ControlKey key) throws IOException {
int protocolCode = key.getProtocolCode();
if(protocolCode > -1) {
BoatActionEnum protocolCode = key.getProtocolCode();
if(protocolCode != BoatActionEnum.NOT_A_STATUS) {
byte[] bytes = new byte[4];
ByteBuffer.wrap(bytes).putInt(key.getProtocolCode());
BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(bytes[3]);
BoatAction boatAction = new BoatAction(protocolCode);
BoatAction boatAction = new BoatAction(boatActionEnum);
//Encode BoatAction.
try {
byte[] encodedBoatAction = RaceVisionByteEncoder.encode(boatAction);
byte[] encodedBoatAction = RaceVisionByteEncoder.boatActionMessage(boatAction);
BinaryMessageEncoder binaryMessage = new BinaryMessageEncoder(MessageType.BOATACTION, System.currentTimeMillis(), 0,
(short) encodedBoatAction.length, encodedBoatAction);
System.out.println("Sending out key: " + protocolCode);
outputStream.write(binaryMessage.getFullMessage());
} catch (InvalidMessageException e) {
Logger.getGlobal().log(Level.WARNING, "Could not encode BoatAction: " + boatAction, e);
}
BinaryMessageEncoder binaryMessage = new BinaryMessageEncoder(MessageType.BOATACTION, System.currentTimeMillis(), 0,
(short) encodedBoatAction.length, encodedBoatAction);
System.out.println("Sending out key: " + boatActionEnum);
outputStream.write(binaryMessage.getFullMessage());
}
}
}
@@ -1,19 +1,23 @@
package visualiser.gameController;
import mock.model.RaceLogic;
import network.BinaryMessageDecoder;
import network.Exceptions.InvalidMessageException;
import network.MessageDecoders.BoatActionDecoder;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.logging.Level;
import java.util.Observable;
import java.util.logging.Logger;
/**
* Service for dispatching key press data to race from client
*/
public class ControllerServer implements Runnable {
public class ControllerServer extends Observable implements Runnable {
/**
* Socket to client
*/
@@ -22,13 +26,23 @@ public class ControllerServer implements Runnable {
* Wrapper for input from client
*/
private DataInputStream inputStream;
/**
* Last received boat action
*/
private BoatActionEnum action;
/**
*
*/
private RaceLogic rc;
/**
* Initialise server-side controller with live client socket
* @param socket to client
*/
public ControllerServer(Socket socket) {
public ControllerServer(Socket socket, RaceLogic rc) {
this.socket = socket;
this.rc = rc;
this.addObserver(rc);
try {
this.inputStream = new DataInputStream(this.socket.getInputStream());
} catch (IOException e) {
@@ -36,6 +50,10 @@ public class ControllerServer implements Runnable {
}
}
public BoatActionEnum getAction() {
return action;
}
/**
* Wait for controller key input from client and loop.
*/
@@ -45,11 +63,26 @@ public class ControllerServer implements Runnable {
byte[] message = new byte[20];
try {
if (inputStream.available() > 0) {
inputStream.read(message);
BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message);
BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody());
BoatActionEnum decodedMessage = boatActionDecoder.getBoatAction();
System.out.println("Received key: " + decodedMessage);
BoatActionDecoder boatActionDecoder = new BoatActionDecoder();
try {
boatActionDecoder.decode(encodedMessage.getMessageBody());
BoatAction boatAction = boatActionDecoder.getMessage();
action = boatAction.getBoatAction();
// Notify observers of most recent action
this.notifyObservers();
this.setChanged();
} catch (InvalidMessageException e) {
Logger.getGlobal().log(Level.WARNING, "Could not decode BoatAction message.", e);
}
}
} catch (IOException e) {
e.printStackTrace();
@@ -1,6 +1,7 @@
package visualiser.gameController.Keys;
import javafx.scene.input.KeyCode;
import network.Messages.Enums.BoatActionEnum;
/**
* Key for the controller, part of the abstract factory KeyFactory
@@ -8,14 +9,14 @@ import javafx.scene.input.KeyCode;
public abstract class ControlKey {
private String name;
protected int protocolCode;
protected BoatActionEnum protocolCode;
/**
* Constructor for key state with specified protocol code
* @param name of action
* @param protocolCode -1 if not sent
* @param protocolCode NOT_A_STATUS if not sent
*/
public ControlKey(String name, int protocolCode) {
public ControlKey(String name, BoatActionEnum protocolCode) {
this.name = name;
this.protocolCode = protocolCode;
}
@@ -26,10 +27,10 @@ public abstract class ControlKey {
*/
public ControlKey(String name){
this.name = name;
this.protocolCode = -1;
this.protocolCode = BoatActionEnum.NOT_A_STATUS;
}
public int getProtocolCode() {
public BoatActionEnum getProtocolCode() {
return protocolCode;
}
@@ -1,5 +1,7 @@
package visualiser.gameController.Keys;
import network.Messages.Enums.BoatActionEnum;
/**
* Key to send downwind packet to server
*/
@@ -11,7 +13,7 @@ public class DownWindKey extends ControlKey {
*
*/
public DownWindKey(String name) {
super(name, 6);
super(name, BoatActionEnum.DOWNWIND);
}
@Override
@@ -27,8 +27,8 @@ public class KeyFactory {
keyState.put("SPACE", new VMGKey("VMG"));
keyState.put("SHIFT", new SailsToggleKey("Toggle Sails"));
keyState.put("ENTER", new TackGybeKey("Tack/Gybe"));
keyState.put("PAGE_UP", new UpWindKey("Upwind"));
keyState.put("PAGE_DOWN", new DownWindKey("Downwind"));
keyState.put("UP", new UpWindKey("Upwind"));
keyState.put("DOWN", new DownWindKey("Downwind"));
}
/**
@@ -1,5 +1,7 @@
package visualiser.gameController.Keys;
import network.Messages.Enums.BoatActionEnum;
/**
* Key to toggle the sails
*/
@@ -11,7 +13,7 @@ public class SailsToggleKey extends ControlKey {
*
*/
public SailsToggleKey(String name) {
super(name, 2);
super(name, BoatActionEnum.SAILS_IN);
}
/**
@@ -19,7 +21,11 @@ public class SailsToggleKey extends ControlKey {
*/
@Override
public void onAction() {
protocolCode = protocolCode == 2? 3 : 2;
if(protocolCode == BoatActionEnum.SAILS_IN) {
protocolCode = BoatActionEnum.SAILS_OUT;
} else {
protocolCode = BoatActionEnum.SAILS_IN;
}
}
@Override
@@ -1,5 +1,7 @@
package visualiser.gameController.Keys;
import network.Messages.Enums.BoatActionEnum;
/**
* key to toggle between tacking and gybing
*/
@@ -11,7 +13,7 @@ public class TackGybeKey extends ControlKey {
*
*/
public TackGybeKey(String name) {
super(name, 4);
super(name, BoatActionEnum.TACK_GYBE);
}
@Override
@@ -1,5 +1,7 @@
package visualiser.gameController.Keys;
import network.Messages.Enums.BoatActionEnum;
/**
* Key to go upwind
*/
@@ -11,7 +13,7 @@ public class UpWindKey extends ControlKey {
*
*/
public UpWindKey(String name) {
super(name, 5);
super(name, BoatActionEnum.UPWIND);
}
@Override
@@ -1,6 +1,7 @@
package visualiser.gameController.Keys;
import javafx.scene.input.KeyCode;
import network.Messages.Enums.BoatActionEnum;
/**
* Key to trigger auto VMG
@@ -13,7 +14,7 @@ public class VMGKey extends ControlKey{
* @param name name of the key
*/
public VMGKey(String name) {
super(name, 1);
super(name, BoatActionEnum.AUTO_PILOT);
}
@Override
@@ -12,7 +12,6 @@ import shared.model.GPSCoordinate;
import shared.model.Mark;
import shared.model.RaceClock;
import java.time.Duration;
import java.util.List;
/**
@@ -39,12 +38,6 @@ public class ResizableRaceCanvas extends ResizableCanvas {
*/
private VisualiserRace visualiserRace;
/**
* The background of the race.
* We render the background whenever the race boundary changes, or the screen size changes.
*/
private Image background;
private boolean annoName = true;
private boolean annoAbbrev = true;
@@ -54,22 +47,15 @@ public class ResizableRaceCanvas extends ResizableCanvas {
private boolean annoTimeSinceLastMark = true;
/**
* The wind arrow node.
*/
private Node arrow;
/**
* Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRace}.
* @param visualiserRace The race that data is read from in order to be drawn.
* @param arrow The wind arrow's node.
*/
public ResizableRaceCanvas(VisualiserRace visualiserRace, Node arrow) {
public ResizableRaceCanvas(VisualiserRace visualiserRace) {
super();
this.visualiserRace = visualiserRace;
this.arrow = arrow;
RaceDataSource raceData = visualiserRace.getRaceDataSource();
@@ -375,32 +361,6 @@ public class ResizableRaceCanvas extends ResizableCanvas {
/**
* Displays an arrow representing wind direction on the Canvas.
* This function accepts a wind-to bearing, but displays a wind-from bearing.
*
* @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up).
* @see GraphCoordinate
*/
private void displayWindArrow(double angle) {
//We need to display wind-from, so add 180 degrees.
angle += 180d;
//Get it within [0, 360).
while (angle >= 360d) {
angle -= 360d;
}
//Rotate the wind arrow.
if (arrow != null && arrow.getRotate() != angle) {
arrow.setRotate(angle);
}
}
/**
* Draws all of the {@link Mark}s on the canvas.
*/
@@ -440,13 +400,9 @@ public class ResizableRaceCanvas extends ResizableCanvas {
this.map.setWidth((int) getWidth());
this.map.setHeight((int) getHeight());
//Redraw the boundary.
redrawBoundaryImage();
//Draw the race.
drawRace();
}
@@ -459,15 +415,13 @@ public class ResizableRaceCanvas extends ResizableCanvas {
/**
* Draws the race boundary, and saves the image to {@link #background}.
* You should call {@link #clear()} before calling this.
* Draws the race boundary.
*/
private void redrawBoundaryImage() {
private void drawBoundary() {
//Prepare to draw.
gc.setLineWidth(1);
gc.setFill(Color.AQUA);
gc.drawImage(new Image(getClass().getClassLoader().getResourceAsStream("images/WaterBackground.png")), 0, 0);
//Calculate the screen coordinates of the boundary.
@@ -487,9 +441,6 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//Draw the boundary.
gc.fillPolygon(xpoints, ypoints, xpoints.length);
//Render boundary to image.
this.background = snapshot(null, null);
}
/**
@@ -511,18 +462,6 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//Marks.
drawMarks();
//Wind arrow. This rotates the wind arrow node.
displayWindArrow(this.visualiserRace.getWindDirection().degrees());
}
/**
* Draws the race boundary image onto the canvas.
* See {@link #background}.
*/
private void drawBoundary() {
gc.drawImage(this.background, 0, 0);
}
@@ -16,7 +16,6 @@ import shared.dataInput.RegattaDataSource;
import shared.model.*;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -27,7 +26,7 @@ import java.util.Map;
* Has a course, boats, boundaries, etc...
* Observes LatestMessages and updates its state based on new messages.
*/
public class VisualiserRace extends Race {
public class VisualiserRace extends Race implements Runnable {
/**
@@ -198,24 +197,24 @@ public class VisualiserRace extends Race {
if (boatLocation != null && boatStatus != null) {
//Get the new position.
double latitude = boatLocation.getLatitudeDouble();
double longitude = boatLocation.getLongitudeDouble();
double latitude = boatLocation.getLatitude();
double longitude = boatLocation.getLongitude();
GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude);
boat.setCurrentPosition(gpsCoordinate);
//Bearing.
boat.setBearing(Bearing.fromDegrees(boatLocation.getHeadingDegrees()));
boat.setBearing(boatLocation.getHeading());
//Time until next mark.
boat.setEstimatedTimeAtNextMark(raceClock.getLocalTime(boatStatus.getEstTimeAtNextMark()));
//Speed.
boat.setCurrentSpeed(boatLocation.getBoatSOG() / Constants.KnotsToMMPerSecond);
boat.setCurrentSpeed(boatLocation.getBoatSpeedKnots());
//Boat status.
BoatStatusEnum newBoatStatusEnum = BoatStatusEnum.fromByte(boatStatus.getBoatStatus());
BoatStatusEnum newBoatStatusEnum = boatStatus.getBoatStatus();
//If we are changing from non-racing to racing, we need to initialise boat with their time at last mark.
if ((boat.getStatus() != BoatStatusEnum.RACING) && (newBoatStatusEnum == BoatStatusEnum.RACING)) {
@@ -292,8 +291,8 @@ public class VisualiserRace extends Race {
if (boatLocation != null) {
//We only update the boat's position.
double latitude = boatLocation.getLatitudeDouble();
double longitude = boatLocation.getLongitudeDouble();
double latitude = boatLocation.getLatitude();
double longitude = boatLocation.getLongitude();
GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude);
mark.setPosition(gpsCoordinate);
@@ -309,13 +308,12 @@ public class VisualiserRace extends Race {
private void updateRaceStatus(RaceStatus raceStatus) {
if (raceStatus != null) {
//Race status enum.
this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus());
this.raceStatusEnum = raceStatus.getRaceStatus();
//Wind bearing.
this.windDirection.setDegrees(raceStatus.getScaledWindDirection());
//Wind speed.
this.windSpeed = raceStatus.getWindSpeedKnots();
//Wind.
this.setWind(
raceStatus.getWindDirection(),
raceStatus.getWindSpeed() );
//Current race time.
this.raceClock.setUTCTime(raceStatus.getCurrentTime());