Merge branch 'master' into story_64_sails
# Conflicts: # racevisionGame/src/main/java/mock/app/MockOutput.java # racevisionGame/src/main/java/mock/model/MockRace.java # racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java # racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java # racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java # racevisionGame/src/main/java/visualiser/app/VisualiserInput.java # racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java # racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java # racevisionGame/src/main/java/visualiser/model/VisualiserRace.java # racevisionGame/src/main/resources/visualiser/scenes/connect.fxml # racevisionGame/src/main/resources/visualiser/scenes/lobby.fxml # racevisionGame/src/test/java/mock/model/MockRaceTest.java # racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java # racevisionGame/src/test/java/network/XMLMessageEncoderTest.java
This commit is contained in:
@@ -40,6 +40,7 @@ public class Annotations {
|
||||
private static String pathCheckAnno = "showBoatPath";
|
||||
private static String timeCheckAnno = "showTime";
|
||||
private static String estTimeCheckAnno = "showEstTime";
|
||||
private static String guideLineAnno = "showGuideline";
|
||||
|
||||
// string values match the fx:id value of radio buttons
|
||||
private static String noBtn = "noBtn";
|
||||
@@ -160,6 +161,16 @@ public class Annotations {
|
||||
raceMap.draw();
|
||||
}
|
||||
});
|
||||
|
||||
//listener to show estimated time for annotation
|
||||
checkBoxes.get(guideLineAnno).selectedProperty()
|
||||
.addListener((ov, old_val, new_val) -> {
|
||||
if (old_val != new_val) {
|
||||
raceMap.toggleGuideLine();
|
||||
storeCurrentAnnotationState(guideLineAnno, new_val);
|
||||
raceMap.draw();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,16 +19,14 @@ public class Ping implements Runnable {
|
||||
this.rc = rc;
|
||||
}
|
||||
|
||||
public boolean pingPort(){
|
||||
//TODO the connection needs to be moved to its own thread, so it doesn't block fx thread.
|
||||
public boolean pingPort() {
|
||||
InetSocketAddress i = new InetSocketAddress(hostname, port);
|
||||
try (Socket s = new Socket()){
|
||||
s.connect(i, 750);//TODO this should be at least a second or two, once moved to its own thread
|
||||
s.connect(i, 1500);
|
||||
s.shutdownInput();
|
||||
s.shutdownOutput();
|
||||
s.close();
|
||||
rc.statusProperty().set("Ready");
|
||||
//System.out.println(String.valueOf(s.isClosed()));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
rc.statusProperty().set("Offline");
|
||||
|
||||
@@ -11,22 +11,22 @@ public class RaceMap {
|
||||
/**
|
||||
* The longitude of the left side of the map.
|
||||
*/
|
||||
private final double longLeft;
|
||||
private double longLeft;
|
||||
|
||||
/**
|
||||
* The longitude of the right side of the map.
|
||||
*/
|
||||
private final double longRight;
|
||||
private double longRight;
|
||||
|
||||
/**
|
||||
* The latitude of the top side of the map.
|
||||
*/
|
||||
private final double latTop;
|
||||
private double latTop;
|
||||
|
||||
/**
|
||||
* The latitude of the bottom side of the map.
|
||||
*/
|
||||
private final double latBottom;
|
||||
private double latBottom;
|
||||
|
||||
|
||||
/**
|
||||
@@ -143,4 +143,27 @@ public class RaceMap {
|
||||
public void setHeight(int height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the bottom right GPS coordinates of the RaceMap.
|
||||
* @param bottomRight New bottom right GPS coordinates.
|
||||
*/
|
||||
public void setGPSBotRight(GPSCoordinate bottomRight) {
|
||||
this.latBottom = bottomRight.getLatitude();
|
||||
this.longRight = bottomRight.getLongitude();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the top left GPS coordinates of the RaceMap.
|
||||
* @param topLeft New top left GPS coordinates.
|
||||
*/
|
||||
public void setGPSTopLeft(GPSCoordinate topLeft) {
|
||||
this.latTop = topLeft.getLatitude();
|
||||
this.longLeft = topLeft.getLongitude();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import shared.dataInput.RaceDataSource;
|
||||
import shared.enums.RoundingType;
|
||||
import shared.model.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -27,29 +28,34 @@ import java.util.List;
|
||||
public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
|
||||
private RaceMap map; // for converting GPSCoordinates to GraphCoordinates
|
||||
private VisualiserRace visualiserRace; // draw data read from this race
|
||||
private Image background;
|
||||
private Image sailsRight = new Image("/images/sailsRight.png");
|
||||
private Image sailsLeft = new Image("/images/sailsLeft.png");
|
||||
private Image sailsLuff = new Image("/images/sailsLuff.gif", 25, 10, false, false);
|
||||
|
||||
// annotations
|
||||
/**
|
||||
* The race we read data from and draw.
|
||||
*/
|
||||
private VisualiserRaceEvent visualiserRace;
|
||||
|
||||
|
||||
private boolean annoName = true;
|
||||
private boolean annoAbbrev = true;
|
||||
private boolean annoSpeed = true;
|
||||
private boolean annoPath = true;
|
||||
private boolean annoEstTime = true;
|
||||
private boolean annoTimeSinceLastMark = true;
|
||||
private boolean annoGuideLine = false;
|
||||
|
||||
/**
|
||||
* Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRace}.
|
||||
* Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRaceEvent}.
|
||||
* @param visualiserRace The race that data is read from in order to be drawn.
|
||||
*/
|
||||
public ResizableRaceCanvas(VisualiserRace visualiserRace) {
|
||||
public ResizableRaceCanvas(VisualiserRaceEvent visualiserRace) {
|
||||
super();
|
||||
this.visualiserRace = visualiserRace;
|
||||
|
||||
RaceDataSource raceData = visualiserRace.getRaceDataSource();
|
||||
RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
|
||||
|
||||
double lat1 = raceData.getMapTopLeft().getLatitude();
|
||||
double long1 = raceData.getMapTopLeft().getLongitude();
|
||||
@@ -99,6 +105,16 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
annoSpeed = !annoSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the guideline annotation
|
||||
*/
|
||||
public void toggleGuideLine() {
|
||||
annoGuideLine = !annoGuideLine;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Rotates things displayed on the canvas. Note: this must be called in
|
||||
* between gc.save() and gc.restore() else they will rotate everything
|
||||
@@ -241,18 +257,19 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
boat.getCountry(),
|
||||
boat.getCurrentSpeed(),
|
||||
this.map.convertGPS(boat.getCurrentPosition()),
|
||||
boat.getTimeToNextMarkFormatted(this.visualiserRace.getRaceClock().getCurrentTime()),
|
||||
boat.getTimeSinceLastMarkFormatted(this.visualiserRace.getRaceClock().getCurrentTime())
|
||||
);
|
||||
boat.getTimeToNextMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()),
|
||||
boat.getTimeSinceLastMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws all of the boats on the canvas.
|
||||
*/
|
||||
private void drawBoats() {
|
||||
for (VisualiserBoat boat : visualiserRace.getBoats()) {
|
||||
//Draw track.
|
||||
drawTrack(boat);
|
||||
|
||||
for (VisualiserBoat boat : new ArrayList<>(visualiserRace.getVisualiserRaceState().getBoats())) {
|
||||
|
||||
//Draw the boat.
|
||||
drawBoat(boat);
|
||||
|
||||
//Only draw wake if they are currently racing.
|
||||
if (boat.getStatus() == BoatStatusEnum.RACING) {
|
||||
@@ -264,7 +281,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
|
||||
//If the race hasn't started, we set the time since last mark to the current time, to ensure we don't start counting until the race actually starts.
|
||||
if ((boat.getStatus() != BoatStatusEnum.RACING) && (boat.getStatus() == BoatStatusEnum.FINISHED)) {
|
||||
boat.setTimeAtLastMark(visualiserRace.getRaceClock().getCurrentTime());
|
||||
boat.setTimeAtLastMark(visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime());
|
||||
}
|
||||
|
||||
//Draw boat label.
|
||||
@@ -278,40 +295,71 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
*/
|
||||
private void drawBoat(VisualiserBoat boat) {
|
||||
|
||||
//The position may be null if we haven't received any BoatLocation messages yet.
|
||||
if (boat.getCurrentPosition() != null) {
|
||||
if (boat.isClientBoat()) {
|
||||
drawClientBoat(boat);
|
||||
}
|
||||
|
||||
//Convert position to graph coordinate.
|
||||
GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition());
|
||||
//Convert position to graph coordinate.
|
||||
GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition());
|
||||
|
||||
//The x coordinates of each vertex of the boat.
|
||||
double[] x = {
|
||||
pos.getX() - 6,
|
||||
pos.getX(),
|
||||
pos.getX() + 6 };
|
||||
//The x coordinates of each vertex of the boat.
|
||||
double[] x = {
|
||||
pos.getX() - 6,
|
||||
pos.getX(),
|
||||
pos.getX() + 6};
|
||||
|
||||
//The y coordinates of each vertex of the boat.
|
||||
double[] y = {
|
||||
pos.getY() + 12,
|
||||
pos.getY() - 12,
|
||||
pos.getY() + 12 };
|
||||
//The y coordinates of each vertex of the boat.
|
||||
double[] y = {
|
||||
pos.getY() + 12,
|
||||
pos.getY() - 12,
|
||||
pos.getY() + 12};
|
||||
|
||||
//The above shape is essentially a triangle 12px wide, and 24px long.
|
||||
//The above shape is essentially a triangle 12px wide, and 24px long.
|
||||
|
||||
//Draw the boat.
|
||||
gc.setFill(boat.getColor());
|
||||
//Draw the boat.
|
||||
gc.setFill(boat.getColor());
|
||||
|
||||
gc.save();
|
||||
rotate(boat.getBearing().degrees(), pos.getX(), pos.getY());
|
||||
gc.fillPolygon(x, y, 3);
|
||||
gc.restore();
|
||||
gc.save();
|
||||
rotate(boat.getBearing().degrees(), pos.getX(), pos.getY());
|
||||
gc.fillPolygon(x, y, 3);
|
||||
gc.restore();
|
||||
|
||||
if (boat.getSourceID() == ThisBoat.getInstance().getSourceID()) {
|
||||
drawSails(boat);
|
||||
}
|
||||
if (boat.getSourceID() == ThisBoat.getInstance().getSourceID()) {
|
||||
drawSails(boat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws extra decorations to show which boat has been assigned to the client.
|
||||
* @param boat The client's boat.
|
||||
*/
|
||||
private void drawClientBoat(VisualiserBoat boat) {
|
||||
|
||||
//Convert position to graph coordinate.
|
||||
GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition());
|
||||
|
||||
//The x coordinates of each vertex of the boat.
|
||||
double[] x = {
|
||||
pos.getX() - 9,
|
||||
pos.getX(),
|
||||
pos.getX() + 9 };
|
||||
|
||||
//The y coordinates of each vertex of the boat.
|
||||
double[] y = {
|
||||
pos.getY() + 15,
|
||||
pos.getY() - 15,
|
||||
pos.getY() + 15 };
|
||||
|
||||
//The above shape is essentially a triangle 24px wide, and 48 long.
|
||||
|
||||
//Draw the boat.
|
||||
gc.setFill(Color.BLACK);
|
||||
|
||||
gc.save();
|
||||
rotate(boat.getBearing().degrees(), pos.getX(), pos.getY());
|
||||
gc.fillPolygon(x, y, 3);
|
||||
gc.restore();
|
||||
|
||||
/**
|
||||
* Draws sails for a given boat on the canvas. Sail position is
|
||||
* determined by the boats heading and the current wind direction
|
||||
@@ -324,7 +372,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
double xPos = boatPos.getX(); // x pos of sail (on boat)
|
||||
double yPos = boatPos.getY() - 6; // y pos of sail (on boat)
|
||||
double boatBearing = boat.getBearing().degrees();
|
||||
double windDirection = visualiserRace.getWindDirection().degrees();
|
||||
double windDirection = 0; //visualiserRace.getWindDirection().degrees();
|
||||
double sailRotateAngle = 0; // rotation for correct sail display
|
||||
Image sailImage = null;
|
||||
Boolean rightSail = true;
|
||||
@@ -393,7 +441,8 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
* Draws all of the {@link Mark}s on the canvas.
|
||||
*/
|
||||
private void drawMarks() {
|
||||
for (Mark mark : this.visualiserRace.getMarks()) {
|
||||
|
||||
for (Mark mark : new ArrayList<>(visualiserRace.getVisualiserRaceState().getMarks())) {
|
||||
drawMark(mark);
|
||||
}
|
||||
}
|
||||
@@ -441,7 +490,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
|
||||
|
||||
//Calculate the screen coordinates of the boundary.
|
||||
List<GPSCoordinate> boundary = this.visualiserRace.getBoundary();
|
||||
List<GPSCoordinate> boundary = new ArrayList<>(visualiserRace.getVisualiserRaceState().getBoundary());
|
||||
double[] xpoints = new double[boundary.size()];
|
||||
double[] ypoints = new double[boundary.size()];
|
||||
|
||||
@@ -464,13 +513,20 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
* Called once per frame, and on canvas resize.
|
||||
*/
|
||||
public void drawRace() {
|
||||
|
||||
//Update RaceMap with new GPS values of race.
|
||||
this.map.setGPSTopLeft(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapTopLeft());
|
||||
this.map.setGPSBotRight(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapBottomRight());
|
||||
|
||||
gc.setLineWidth(2);
|
||||
clear(); // clear the previous canvas
|
||||
|
||||
drawBoundary();
|
||||
|
||||
//Guiding Line
|
||||
drawRaceLine();
|
||||
if (annoGuideLine){
|
||||
drawRaceLine();
|
||||
}
|
||||
|
||||
//Boats.
|
||||
drawBoats();
|
||||
@@ -481,7 +537,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
* draws a transparent line around the course that shows the paths boats must travel
|
||||
*/
|
||||
public void drawRaceLine(){
|
||||
List<Leg> legs = this.visualiserRace.getLegs();
|
||||
List<Leg> legs = this.visualiserRace.getVisualiserRaceState().getLegs();
|
||||
GPSCoordinate legStartPoint = legs.get(0).getStartCompoundMark().getAverageGPSCoordinate();
|
||||
GPSCoordinate nextStartPoint;
|
||||
for (int i = 0; i < legs.size() -1; i++) {
|
||||
@@ -495,6 +551,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
* draws the line leg by leg
|
||||
* @param legs the legs of a race
|
||||
* @param index the index of the current leg to use
|
||||
* @param legStartPoint The position the current leg.
|
||||
* @return the end point of the current leg that has been drawn
|
||||
*/
|
||||
private GPSCoordinate drawLineRounding(List<Leg> legs, int index, GPSCoordinate legStartPoint){
|
||||
@@ -627,7 +684,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
gc.setFill(boat.getColor());
|
||||
|
||||
//Draw each TrackPoint.
|
||||
for (TrackPoint point : boat.getTrack()) {
|
||||
for (TrackPoint point : new ArrayList<>(boat.getTrack())) {
|
||||
|
||||
//Convert the GPSCoordinate to a screen coordinate.
|
||||
GraphCoordinate scaledCoordinate = this.map.convertGPS(point.getCoordinate());
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package visualiser.model;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.paint.Color;
|
||||
import shared.model.Leg;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
@@ -23,7 +28,7 @@ public class Sparkline {
|
||||
/**
|
||||
* The race to observe.
|
||||
*/
|
||||
private VisualiserRace race;
|
||||
private VisualiserRaceState race;
|
||||
|
||||
/**
|
||||
* The boats to observe.
|
||||
@@ -31,10 +36,10 @@ public class Sparkline {
|
||||
private ObservableList<VisualiserBoat> boats;
|
||||
|
||||
/**
|
||||
* The number of legs in the race.
|
||||
* Used to correctly scale the linechart.
|
||||
* Race legs to observe.
|
||||
* We need to observe legs as they may be added after the sparkline is created if race.xml is received after this is created.
|
||||
*/
|
||||
private Integer legNum;
|
||||
private ObservableList<Leg> legs;
|
||||
|
||||
|
||||
/**
|
||||
@@ -52,21 +57,31 @@ public class Sparkline {
|
||||
*/
|
||||
private NumberAxis yAxis;
|
||||
|
||||
/**
|
||||
* A map between a boat and its data series in the sparkline.
|
||||
* This is used so that we can remove a series when (or if) a boat is removed from the race.
|
||||
*/
|
||||
private Map<VisualiserBoat, XYChart.Series<Number, Number>> boatSeriesMap;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructor to set up initial sparkline (LineChart) object
|
||||
* @param race The race to listen to.
|
||||
* @param sparklineChart JavaFX LineChart for the sparkline.
|
||||
*/
|
||||
public Sparkline(VisualiserRace race, LineChart<Number,Number> sparklineChart) {
|
||||
public Sparkline(VisualiserRaceState race, LineChart<Number, Number> sparklineChart) {
|
||||
this.race = race;
|
||||
this.boats = race.getBoats();
|
||||
this.legNum = race.getLegCount();
|
||||
this.boats = new SortedList<>(race.getBoats());
|
||||
this.legs = race.getLegs();
|
||||
|
||||
this.sparklineChart = sparklineChart;
|
||||
this.yAxis = (NumberAxis) sparklineChart.getYAxis();
|
||||
this.xAxis = (NumberAxis) sparklineChart.getXAxis();
|
||||
|
||||
this.boatSeriesMap = new HashMap<>();
|
||||
|
||||
createSparkline();
|
||||
|
||||
}
|
||||
@@ -78,50 +93,45 @@ public class Sparkline {
|
||||
* Position numbers are displayed.
|
||||
*/
|
||||
private void createSparkline() {
|
||||
// NOTE: Y axis is in negatives to display correct positions
|
||||
|
||||
//For each boat...
|
||||
for (VisualiserBoat boat : this.boats) {
|
||||
//We need to dynamically update the sparkline when boats are added/removed.
|
||||
boats.addListener((ListChangeListener.Change<? extends VisualiserBoat> c) -> {
|
||||
|
||||
//Create data series for each boat.
|
||||
XYChart.Series<Number, Number> series = new XYChart.Series<>();
|
||||
Platform.runLater(() -> {
|
||||
|
||||
while (c.next()) {
|
||||
|
||||
if (c.wasAdded()) {
|
||||
for (VisualiserBoat boat : c.getAddedSubList()) {
|
||||
addBoatSeries(boat);
|
||||
}
|
||||
|
||||
} else if (c.wasRemoved()) {
|
||||
for (VisualiserBoat boat : c.getRemoved()) {
|
||||
removeBoatSeries(boat);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Update height of y axis.
|
||||
setYAxisLowerBound();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
//All boats start in "last" place.
|
||||
series.getData().add(new XYChart.Data<>(0, boats.size()));
|
||||
|
||||
//Listen for changes in the boat's leg - we only update the graph when it changes leg.
|
||||
boat.legProperty().addListener(
|
||||
(observable, oldValue, newValue) -> {
|
||||
|
||||
//Get the data to plot.
|
||||
List<VisualiserBoat> boatOrder = race.getLegCompletionOrder().get(oldValue);
|
||||
//Find boat position in list.
|
||||
int boatPosition = boatOrder.indexOf(boat) + 1;
|
||||
|
||||
//Get leg number.
|
||||
int legNumber = oldValue.getLegNumber() + 1;
|
||||
|
||||
|
||||
//Create new data point for boat's position at the new leg.
|
||||
XYChart.Data<Number, Number> dataPoint = new XYChart.Data<>(legNumber, boatPosition);
|
||||
|
||||
//Add to series.
|
||||
Platform.runLater(() -> series.getData().add(dataPoint));
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
//Add to chart.
|
||||
sparklineChart.getData().add(series);
|
||||
|
||||
//Color using boat's color. We need to do this after adding the series to a chart, otherwise we get null pointer exceptions.
|
||||
series.getNode().setStyle("-fx-stroke: " + colourToHex(boat.getColor()) + ";");
|
||||
legs.addListener((ListChangeListener.Change<? extends Leg> c) -> {
|
||||
Platform.runLater(() -> xAxis.setUpperBound(race.getLegCount()));
|
||||
});
|
||||
|
||||
|
||||
//Initialise chart for existing boats.
|
||||
for (VisualiserBoat boat : boats) {
|
||||
addBoatSeries(boat);
|
||||
}
|
||||
|
||||
|
||||
sparklineChart.setCreateSymbols(false);
|
||||
|
||||
//Set x axis details
|
||||
@@ -130,20 +140,109 @@ public class Sparkline {
|
||||
xAxis.setTickLabelsVisible(false);
|
||||
xAxis.setMinorTickVisible(false);
|
||||
xAxis.setLowerBound(0);
|
||||
xAxis.setUpperBound(legNum + 2);
|
||||
xAxis.setUpperBound(race.getLegCount());
|
||||
xAxis.setTickUnit(1);
|
||||
|
||||
|
||||
//The y-axis uses negative values, with the minus sign hidden (e.g., boat in 1st has position -1, which becomes 1, boat in 6th has position -6, which becomes 6).
|
||||
//This is necessary to actually get the y-axis labelled correctly. Negative tick count doesn't work.
|
||||
//Set y axis details
|
||||
yAxis.setLowerBound(boats.size());
|
||||
yAxis.setUpperBound(1);
|
||||
yAxis.setAutoRanging(false);
|
||||
|
||||
yAxis.setTickUnit(1);
|
||||
yAxis.setMinorTickCount(0);
|
||||
|
||||
yAxis.setUpperBound(0);
|
||||
setYAxisLowerBound();
|
||||
|
||||
yAxis.setLabel("Position in Race");
|
||||
yAxis.setTickUnit(-1);//Negative tick reverses the y axis.
|
||||
|
||||
yAxis.setTickMarkVisible(true);
|
||||
yAxis.setTickLabelsVisible(true);
|
||||
yAxis.setTickMarkVisible(true);
|
||||
yAxis.setMinorTickVisible(true);
|
||||
yAxis.setMinorTickVisible(false);
|
||||
|
||||
|
||||
|
||||
//Hide minus number from displaying on axis.
|
||||
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
|
||||
@Override
|
||||
public String toString(Number value) {
|
||||
if ((value.intValue() == 0) || (value.intValue() < -boats.size())) {
|
||||
return "";
|
||||
|
||||
} else {
|
||||
return String.format("%d", -value.intValue());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the lower bound of the y-axis.
|
||||
*/
|
||||
private void setYAxisLowerBound() {
|
||||
yAxis.setLowerBound( -(boats.size() + 1));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Removes the data series for a given boat from the sparkline.
|
||||
* @param boat Boat to remove series for.
|
||||
*/
|
||||
private void removeBoatSeries(VisualiserBoat boat) {
|
||||
sparklineChart.getData().remove(boatSeriesMap.get(boat));
|
||||
boatSeriesMap.remove(boat);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a data series for a boat, and adds it to the sparkline.
|
||||
* @param boat Boat to add series for.
|
||||
*/
|
||||
private void addBoatSeries(VisualiserBoat boat) {
|
||||
|
||||
//Create data series for boat.
|
||||
XYChart.Series<Number, Number> series = new XYChart.Series<>();
|
||||
|
||||
|
||||
//All boats start in "last" place.
|
||||
series.getData().add(new XYChart.Data<>(0, -(boats.size())));
|
||||
|
||||
//Listen for changes in the boat's leg - we only update the graph when it changes leg.
|
||||
boat.legProperty().addListener(
|
||||
(observable, oldValue, newValue) -> {
|
||||
|
||||
//Get the data to plot.
|
||||
List<VisualiserBoat> boatOrder = race.getLegCompletionOrder().get(oldValue);
|
||||
//Find boat position in list.
|
||||
int boatPosition = -(boatOrder.indexOf(boat) + 1);
|
||||
|
||||
//Get leg number.
|
||||
int legNumber = oldValue.getLegNumber() + 1;
|
||||
|
||||
|
||||
//Create new data point for boat's position at the new leg.
|
||||
XYChart.Data<Number, Number> dataPoint = new XYChart.Data<>(legNumber, boatPosition);
|
||||
|
||||
//Add to series.
|
||||
Platform.runLater(() -> series.getData().add(dataPoint));
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
//Add to chart.
|
||||
sparklineChart.getData().add(series);
|
||||
|
||||
//Color using boat's color. We need to do this after adding the series to a chart, otherwise we get null pointer exceptions.
|
||||
series.getNode().setStyle("-fx-stroke: " + colourToHex(boat.getColor()) + ";");
|
||||
|
||||
|
||||
boatSeriesMap.put(boat, series);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -55,6 +55,11 @@ public class VisualiserBoat extends Boat {
|
||||
*/
|
||||
private static final double wakeScale = 5;
|
||||
|
||||
/**
|
||||
* If true then this boat has been allocated to the client.
|
||||
*/
|
||||
private boolean isClientBoat = false;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -167,28 +172,33 @@ public class VisualiserBoat extends Boat {
|
||||
*/
|
||||
public String getTimeToNextMarkFormatted(ZonedDateTime currentTime) {
|
||||
|
||||
//Calculate time delta.
|
||||
Duration timeUntil = Duration.between(currentTime, getEstimatedTimeAtNextMark());
|
||||
if ((getTimeAtLastMark() != null) && (currentTime != null)) {
|
||||
//Calculate time delta.
|
||||
Duration timeUntil = Duration.between(currentTime, getEstimatedTimeAtNextMark());
|
||||
|
||||
//Convert to seconds.
|
||||
long secondsUntil = timeUntil.getSeconds();
|
||||
//Convert to seconds.
|
||||
long secondsUntil = timeUntil.getSeconds();
|
||||
|
||||
//This means the estimated time is in the past, or not racing.
|
||||
if ((secondsUntil < 0) || (getStatus() != BoatStatusEnum.RACING)) {
|
||||
return " -";
|
||||
}
|
||||
//This means the estimated time is in the past, or not racing.
|
||||
if ((secondsUntil < 0) || (getStatus() != BoatStatusEnum.RACING)) {
|
||||
return " -";
|
||||
}
|
||||
|
||||
|
||||
if (secondsUntil <= 60) {
|
||||
//If less than 1 minute, display seconds only.
|
||||
return " " + secondsUntil + "s";
|
||||
if (secondsUntil <= 60) {
|
||||
//If less than 1 minute, display seconds only.
|
||||
return " " + secondsUntil + "s";
|
||||
|
||||
} else {
|
||||
//Otherwise display minutes and seconds.
|
||||
long seconds = secondsUntil % 60;
|
||||
long minutes = (secondsUntil - seconds) / 60;
|
||||
return String.format(" %dm %ds", minutes, seconds);
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
//Otherwise display minutes and seconds.
|
||||
long seconds = secondsUntil % 60;
|
||||
long minutes = (secondsUntil - seconds) / 60;
|
||||
return String.format(" %dm %ds", minutes, seconds);
|
||||
|
||||
return " -";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -201,7 +211,7 @@ public class VisualiserBoat extends Boat {
|
||||
*/
|
||||
public String getTimeSinceLastMarkFormatted(ZonedDateTime currentTime) {
|
||||
|
||||
if (getTimeAtLastMark() != null) {
|
||||
if ((getTimeAtLastMark() != null) && (currentTime != null)) {
|
||||
//Calculate time delta.
|
||||
Duration timeSince = Duration.between(getTimeAtLastMark(), currentTime);
|
||||
|
||||
@@ -212,4 +222,21 @@ public class VisualiserBoat extends Boat {
|
||||
return " -";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not this boat has been assigned to the client.
|
||||
* @return True if this is the client's boat.
|
||||
*/
|
||||
public boolean isClientBoat() {
|
||||
return isClientBoat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this boat has been assigned to the client.
|
||||
* @param clientBoat True if this is the client's boat.
|
||||
*/
|
||||
public void setClientBoat(boolean clientBoat) {
|
||||
isClientBoat = clientBoat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,469 +0,0 @@
|
||||
package visualiser.model;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.paint.Color;
|
||||
import network.Messages.BoatLocation;
|
||||
import network.Messages.BoatStatus;
|
||||
import network.Messages.Enums.BoatStatusEnum;
|
||||
import network.Messages.Enums.RaceStatusEnum;
|
||||
import network.Messages.LatestMessages;
|
||||
import network.Messages.RaceStatus;
|
||||
import shared.dataInput.BoatDataSource;
|
||||
import shared.dataInput.RaceDataSource;
|
||||
import shared.dataInput.RegattaDataSource;
|
||||
import shared.model.*;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The Class used to view the race streamed.
|
||||
* Has a course, boats, boundaries, etc...
|
||||
* Observes LatestMessages and updates its state based on new messages.
|
||||
*/
|
||||
public class VisualiserRace extends Race implements Runnable {
|
||||
|
||||
|
||||
/**
|
||||
* An observable list of boats in the race.
|
||||
*/
|
||||
private final ObservableList<VisualiserBoat> boats;
|
||||
|
||||
/**
|
||||
* An observable list of marker boats in the race.
|
||||
*/
|
||||
private ObservableList<Mark> boatMarkers;
|
||||
|
||||
|
||||
/**
|
||||
* Maps between a Leg to a list of boats, in the order that they finished the leg.
|
||||
* Used by the Sparkline to ensure it has correct information.
|
||||
*/
|
||||
private Map<Leg, List<VisualiserBoat>> legCompletionOrder = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and receives events from LatestMessages.
|
||||
* @param boatDataSource Data source for boat related data (yachts and marker boats).
|
||||
* @param raceDataSource Data source for race related data (participating boats, legs, etc...).
|
||||
* @param regattaDataSource Data source for race related data (course name, location, timezone, etc...).
|
||||
* @param latestMessages The LatestMessages to send events to.
|
||||
* @param colors A collection of colors used to assign a color to each boat.
|
||||
*/
|
||||
public VisualiserRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, List<Color> colors) {
|
||||
|
||||
super(boatDataSource, raceDataSource, regattaDataSource, latestMessages);
|
||||
|
||||
|
||||
this.boats = FXCollections.observableArrayList(this.generateVisualiserBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), colors));
|
||||
|
||||
this.boatMarkers = FXCollections.observableArrayList(boatDataSource.getMarkerBoats().values());
|
||||
|
||||
|
||||
//Initialise the leg completion order map.
|
||||
for (Leg leg : this.legs) {
|
||||
this.legCompletionOrder.put(leg, new ArrayList<>(this.boats.size()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the race data source for this race to a new RaceDataSource.
|
||||
* Uses the boundary and legs specified by the new RaceDataSource.
|
||||
* @param raceDataSource The new RaceDataSource to use.
|
||||
*/
|
||||
public void setRaceDataSource(RaceDataSource raceDataSource) {
|
||||
this.raceDataSource = raceDataSource;
|
||||
|
||||
this.boundary = raceDataSource.getBoundary();
|
||||
|
||||
this.useLegsList(raceDataSource.getLegs());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the boat data source for this race to a new BoatDataSource.
|
||||
* Uses the marker boats specified by the new BoatDataSource.
|
||||
* @param boatDataSource The new BoatDataSource to use.
|
||||
*/
|
||||
public void setBoatDataSource(BoatDataSource boatDataSource) {
|
||||
this.boatDataSource = boatDataSource;
|
||||
|
||||
this.boatMarkers = FXCollections.observableArrayList(boatDataSource.getMarkerBoats().values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the regatta data source for this race to a new RegattaDataSource.
|
||||
* @param regattaDataSource The new RegattaDataSource to use.
|
||||
*/
|
||||
public void setRegattaDataSource(RegattaDataSource regattaDataSource) {
|
||||
this.regattaDataSource = regattaDataSource;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of {@link Mark} boats.
|
||||
* @return List of mark boats.
|
||||
*/
|
||||
public ObservableList<Mark> getMarks() {
|
||||
return boatMarkers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of VisualiserBoats given a list of Boats, and a list of participating boats.
|
||||
* @param boats The map of Boats describing boats that are potentially in the race. Maps boat sourceID to boat.
|
||||
* @param sourceIDs The list of boat sourceIDs describing which specific boats are actually participating.
|
||||
* @param colors The list of colors to be used for the boats.
|
||||
* @return A list of MockBoats that are participating in the race.
|
||||
*/
|
||||
private List<VisualiserBoat> generateVisualiserBoats(Map<Integer, Boat> boats, List<Integer> sourceIDs, List<Color> colors) {
|
||||
|
||||
List<VisualiserBoat> visualiserBoats = new ArrayList<>(sourceIDs.size());
|
||||
|
||||
//For each sourceID participating...
|
||||
int colorIndex = 0;
|
||||
for (int sourceID : sourceIDs) {
|
||||
|
||||
//Get the boat associated with the sourceID.
|
||||
Boat boat = boats.get(sourceID);
|
||||
|
||||
//Get a color for the boat.
|
||||
Color color = colors.get(colorIndex);
|
||||
|
||||
//Construct a VisualiserBoat using the Boat and Polars.
|
||||
VisualiserBoat visualiserBoat = new VisualiserBoat(boat, color);
|
||||
|
||||
visualiserBoats.add(visualiserBoat);
|
||||
|
||||
//Next color.
|
||||
colorIndex++;
|
||||
|
||||
}
|
||||
|
||||
return visualiserBoats;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialise the boats in the race.
|
||||
* This sets their current leg.
|
||||
*/
|
||||
@Override
|
||||
protected void initialiseBoats() {
|
||||
|
||||
Leg startingLeg = legs.get(0);
|
||||
|
||||
for (VisualiserBoat boat : boats) {
|
||||
|
||||
boat.setCurrentLeg(startingLeg);
|
||||
boat.setTimeAtLastMark(this.raceClock.getCurrentTime());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates all of the racing boats based on messages received.
|
||||
* @param boats The list of racing boats.
|
||||
* @param boatLocationMap A map between boat sourceIDs and BoatLocation messages.
|
||||
* @param boatStatusMap A map between boat sourceIDs and BoatStatus messages.
|
||||
*/
|
||||
private void updateBoats(ObservableList<VisualiserBoat> boats, Map<Integer, BoatLocation> boatLocationMap, Map<Integer, BoatStatus> boatStatusMap) {
|
||||
|
||||
for (VisualiserBoat boat : boats) {
|
||||
BoatLocation boatLocation = boatLocationMap.get(boat.getSourceID());
|
||||
BoatStatus boatStatus = boatStatusMap.get(boat.getSourceID());
|
||||
updateBoat(boat, boatLocation, boatStatus);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates an individual racing boat based on messages received.
|
||||
* @param boat The boat to update.
|
||||
* @param boatLocation The BoatLocation message to use.
|
||||
* @param boatStatus The BoatStatus message to use.
|
||||
*/
|
||||
private void updateBoat(VisualiserBoat boat, BoatLocation boatLocation, BoatStatus boatStatus) {
|
||||
|
||||
if (boatLocation != null && boatStatus != null) {
|
||||
|
||||
//Get the new position.
|
||||
double latitude = boatLocation.getLatitudeDouble();
|
||||
double longitude = boatLocation.getLongitudeDouble();
|
||||
GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude);
|
||||
|
||||
boat.setCurrentPosition(gpsCoordinate);
|
||||
|
||||
//Bearing.
|
||||
boat.setBearing(Bearing.fromDegrees(boatLocation.getHeadingDegrees()));
|
||||
|
||||
//Time until next mark.
|
||||
boat.setEstimatedTimeAtNextMark(raceClock.getLocalTime(boatStatus.getEstTimeAtNextMark()));
|
||||
|
||||
//Speed.
|
||||
boat.setCurrentSpeed(boatLocation.getBoatSOG() / Constants.KnotsToMMPerSecond);
|
||||
|
||||
|
||||
//Boat status.
|
||||
BoatStatusEnum newBoatStatusEnum = BoatStatusEnum.fromByte(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)) {
|
||||
boat.setTimeAtLastMark(this.raceClock.getCurrentTime());
|
||||
}
|
||||
|
||||
boat.setStatus(newBoatStatusEnum);
|
||||
|
||||
|
||||
//Leg.
|
||||
int legNumber = boatStatus.getLegNumber();
|
||||
|
||||
if (legNumber >= 1 && legNumber < legs.size()) {
|
||||
if (boat.getCurrentLeg() != legs.get(legNumber)) {
|
||||
boatFinishedLeg(boat, legs.get(legNumber));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Attempt to add a track point.
|
||||
if (newBoatStatusEnum == BoatStatusEnum.RACING) {
|
||||
boat.addTrackPoint(boat.getCurrentPosition(), raceClock.getCurrentTime());
|
||||
}
|
||||
|
||||
//Set finish time if boat finished.
|
||||
if (newBoatStatusEnum == BoatStatusEnum.FINISHED || legNumber == this.legs.size()) {
|
||||
boat.setTimeFinished(boatLocation.getTime());
|
||||
boat.setStatus(BoatStatusEnum.FINISHED);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates a boat's leg to a specified leg. Also records the order in which the boat passed the leg.
|
||||
* @param boat The boat to update.
|
||||
* @param leg The leg to use.
|
||||
*/
|
||||
private void boatFinishedLeg(VisualiserBoat boat, Leg leg) {
|
||||
|
||||
//Record order in which boat finished leg.
|
||||
this.legCompletionOrder.get(boat.getCurrentLeg()).add(boat);
|
||||
|
||||
//Update boat.
|
||||
boat.setCurrentLeg(leg);
|
||||
boat.setTimeAtLastMark(this.raceClock.getCurrentTime());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates all of the marker boats based on messages received.
|
||||
* @param boatMarkers The list of marker boats.
|
||||
* @param boatLocationMap A map between boat sourceIDs and BoatLocation messages.
|
||||
* @param boatStatusMap A map between boat sourceIDs and BoatStatus messages.
|
||||
*/
|
||||
private void updateMarkers(ObservableList<Mark> boatMarkers, Map<Integer, BoatLocation> boatLocationMap, Map<Integer, BoatStatus> boatStatusMap) {
|
||||
|
||||
for (Mark mark : boatMarkers) {
|
||||
BoatLocation boatLocation = boatLocationMap.get(mark.getSourceID());
|
||||
updateMark(mark, boatLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an individual marker boat based on messages received.
|
||||
* @param mark The marker boat to be updated.
|
||||
* @param boatLocation The message describing the boat's new location.
|
||||
*/
|
||||
private void updateMark(Mark mark, BoatLocation boatLocation) {
|
||||
|
||||
if (boatLocation != null) {
|
||||
|
||||
//We only update the boat's position.
|
||||
double latitude = boatLocation.getLatitudeDouble();
|
||||
double longitude = boatLocation.getLongitudeDouble();
|
||||
GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude);
|
||||
|
||||
mark.setPosition(gpsCoordinate);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the race status (RaceStatusEnum, wind bearing, wind speed) based on received messages.
|
||||
* @param raceStatus The RaceStatus message received.
|
||||
*/
|
||||
private void updateRaceStatus(RaceStatus raceStatus) {
|
||||
|
||||
//Race status enum.
|
||||
this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus());
|
||||
|
||||
//Wind.
|
||||
this.setWind(
|
||||
Bearing.fromDegrees(raceStatus.getScaledWindDirection()),
|
||||
raceStatus.getWindSpeedKnots() );
|
||||
|
||||
//Current race time.
|
||||
this.raceClock.setUTCTime(raceStatus.getCurrentTime());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Runnable for the thread.
|
||||
*/
|
||||
public void run() {
|
||||
initialiseBoats();
|
||||
startRaceStream();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Starts the race.
|
||||
* This updates the race based on {@link #latestMessages}.
|
||||
*/
|
||||
private void startRaceStream() {
|
||||
|
||||
new AnimationTimer() {
|
||||
|
||||
long lastFrameTime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public void handle(long arg0) {
|
||||
|
||||
//Calculate the frame period.
|
||||
long currentFrameTime = System.currentTimeMillis();
|
||||
long framePeriod = currentFrameTime - lastFrameTime;
|
||||
|
||||
|
||||
//Update race status.
|
||||
updateRaceStatus(latestMessages.getRaceStatus());
|
||||
|
||||
|
||||
//Update racing boats.
|
||||
updateBoats(boats, latestMessages.getBoatLocationMap(), latestMessages.getBoatStatusMap());
|
||||
//And their positions (e.g., 5th).
|
||||
updateBoatPositions(boats);
|
||||
|
||||
|
||||
//Update marker boats.
|
||||
updateMarkers(boatMarkers, latestMessages.getBoatLocationMap(), latestMessages.getBoatStatusMap());
|
||||
|
||||
|
||||
|
||||
|
||||
if (getRaceStatusEnum() == RaceStatusEnum.FINISHED) {
|
||||
stop();
|
||||
}
|
||||
|
||||
lastFrameTime = currentFrameTime;
|
||||
|
||||
//Increment fps.
|
||||
incrementFps(framePeriod);
|
||||
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update position of boats in race (e.g, 5th), no position if on starting leg or DNF.
|
||||
* @param boats The list of boats to update.
|
||||
*/
|
||||
private void updateBoatPositions(ObservableList<VisualiserBoat> boats) {
|
||||
|
||||
//Sort boats.
|
||||
sortBoatsByPosition(boats);
|
||||
|
||||
//Assign new positions.
|
||||
for (int i = 0; i < boats.size(); i++) {
|
||||
VisualiserBoat boat = boats.get(i);
|
||||
|
||||
|
||||
if ((boat.getStatus() == BoatStatusEnum.DNF) || (boat.getStatus() == BoatStatusEnum.PRESTART) || (boat.getCurrentLeg().getLegNumber() < 0)) {
|
||||
|
||||
boat.setPosition("-");
|
||||
|
||||
} else {
|
||||
boat.setPosition(Integer.toString(i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the list of boats by their position within the race.
|
||||
* @param boats The list of boats in the race.
|
||||
*/
|
||||
private void sortBoatsByPosition(ObservableList<VisualiserBoat> boats) {
|
||||
|
||||
FXCollections.sort(boats, (a, b) -> {
|
||||
//Get the difference in leg numbers.
|
||||
int legNumberDelta = b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber();
|
||||
|
||||
//If they're on the same leg, we need to compare time to finish leg.
|
||||
if (legNumberDelta == 0) {
|
||||
return (int) Duration.between(b.getEstimatedTimeAtNextMark(), a.getEstimatedTimeAtNextMark()).toMillis();
|
||||
} else {
|
||||
return legNumberDelta;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the boats participating in the race.
|
||||
* @return ObservableList of boats participating in the race.
|
||||
*/
|
||||
public ObservableList<VisualiserBoat> getBoats() {
|
||||
return boats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the order in which boats completed each leg. Maps the leg to a list of boats, ordered by the order in which they finished the leg.
|
||||
* @return Leg completion order for each leg.
|
||||
*/
|
||||
public Map<Leg, List<VisualiserBoat>> getLegCompletionOrder() {
|
||||
return legCompletionOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an estimated time an event will occur, and converts it to the
|
||||
* number of seconds before the event will occur.
|
||||
*
|
||||
* @param estTimeMillis The estimated time, in milliseconds.
|
||||
* @param currentTime The current time, in milliseconds.
|
||||
* @return int difference between time the race started and the estimated time
|
||||
*/
|
||||
private int convertEstTime(long estTimeMillis, long currentTime) {
|
||||
|
||||
//Calculate millisecond delta.
|
||||
long estElapsedMillis = estTimeMillis - currentTime;
|
||||
|
||||
//Convert milliseconds to seconds.
|
||||
int estElapsedSecs = Math.round(estElapsedMillis / 1000);
|
||||
|
||||
return estElapsedSecs;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package visualiser.model;
|
||||
|
||||
import mock.exceptions.CommandConstructionException;
|
||||
import mock.model.commandFactory.Command;
|
||||
import mock.model.commandFactory.CompositeCommand;
|
||||
import network.Messages.*;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
import visualiser.Commands.VisualiserRaceCommands.VisualiserRaceCommandFactory;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* The controller for race related messages, coming from the server to the client.
|
||||
*/
|
||||
public class VisualiserRaceController implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* Incoming messages from server.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> incomingMessages;
|
||||
|
||||
|
||||
/**
|
||||
* Commands are placed in here, and executed by visualiserRace.
|
||||
*/
|
||||
private CompositeCommand compositeRaceCommand;
|
||||
|
||||
|
||||
/**
|
||||
* The context that created commands operate on.
|
||||
*/
|
||||
private VisualiserRaceState visualiserRace;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a visualiserInput to convert an incoming stream of messages into commands.
|
||||
* @param incomingMessages The incoming queue of messages.
|
||||
* @param visualiserRace The context to for commands to operate on.
|
||||
* @param compositeRaceCommand The composite command to place command in.
|
||||
*/
|
||||
public VisualiserRaceController(BlockingQueue<AC35Data> incomingMessages, VisualiserRaceState visualiserRace, CompositeCommand compositeRaceCommand) {
|
||||
this.incomingMessages = incomingMessages;
|
||||
this.compositeRaceCommand = compositeRaceCommand;
|
||||
this.visualiserRace = visualiserRace;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
try {
|
||||
AC35Data message = incomingMessages.take();
|
||||
|
||||
Command command = VisualiserRaceCommandFactory.create(message, visualiserRace);
|
||||
compositeRaceCommand.addCommand(command);
|
||||
|
||||
} catch (CommandConstructionException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "VisualiserRaceController could not create a command for incoming message.");
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "VisualiserRaceController was interrupted on thread: " + Thread.currentThread() + " while waiting for messages.");
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package visualiser.model;
|
||||
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import mock.model.commandFactory.CompositeCommand;
|
||||
import network.Messages.Enums.RequestToJoinEnum;
|
||||
import shared.dataInput.EmptyBoatDataSource;
|
||||
import shared.dataInput.EmptyRaceDataSource;
|
||||
import shared.dataInput.EmptyRegattaDataSource;
|
||||
import visualiser.gameController.ControllerClient;
|
||||
import visualiser.network.ServerConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
|
||||
|
||||
/**
|
||||
* This class holds a race, and a client's connection to it
|
||||
*/
|
||||
public class VisualiserRaceEvent {
|
||||
|
||||
/**
|
||||
* Our connection to the server.
|
||||
*/
|
||||
private ServerConnection serverConnection;
|
||||
/**
|
||||
* The thread serverConnection is running on.
|
||||
*/
|
||||
private Thread serverConnectionThread;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The race object which describes the currently occurring race.
|
||||
*/
|
||||
private VisualiserRaceState visualiserRaceState;
|
||||
|
||||
|
||||
/**
|
||||
* The service for updating the {@link #visualiserRaceState}.
|
||||
*/
|
||||
private VisualiserRaceService visualiserRaceService;
|
||||
/**
|
||||
* The thread {@link #visualiserRaceService} is running on.
|
||||
*/
|
||||
private Thread visualiserRaceServiceThread;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a visualiser race event, with a given socket and request type.
|
||||
* @param socket The socket to connect to.
|
||||
* @param requestType The type of {@link network.Messages.RequestToJoin} to make.
|
||||
* @throws IOException Thrown if there is a problem with the socket.
|
||||
*/
|
||||
public VisualiserRaceEvent(Socket socket, RequestToJoinEnum requestType) throws IOException {
|
||||
|
||||
this.visualiserRaceState = new VisualiserRaceState(new EmptyRaceDataSource(), new EmptyRegattaDataSource(), new EmptyBoatDataSource());
|
||||
|
||||
|
||||
CompositeCommand raceCommands = new CompositeCommand();
|
||||
this.visualiserRaceService = new VisualiserRaceService(raceCommands, visualiserRaceState);
|
||||
|
||||
this.visualiserRaceServiceThread = new Thread(visualiserRaceService, "VisualiserRaceEvent()->VisualiserRaceService thread " + visualiserRaceService);
|
||||
this.visualiserRaceServiceThread.start();
|
||||
|
||||
|
||||
this.serverConnection = new ServerConnection(socket, visualiserRaceState, raceCommands, requestType);
|
||||
this.serverConnectionThread = new Thread(serverConnection, "StartController.enterLobby()->serverConnection thread " + serverConnection);
|
||||
this.serverConnectionThread.start();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the state of the race.
|
||||
* @return The state of the race.
|
||||
*/
|
||||
public VisualiserRaceState getVisualiserRaceState() {
|
||||
return visualiserRaceState;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the controller client, which writes BoatAction messages to the outgoing queue.
|
||||
* @return The ControllerClient.
|
||||
*/
|
||||
public ControllerClient getControllerClient() {
|
||||
return serverConnection.getControllerClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection to server.
|
||||
* @return Connection to server.
|
||||
*/
|
||||
public ServerConnection getServerConnection() {
|
||||
return serverConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the framerate property of the race.
|
||||
* @return Framerate property of race.
|
||||
*/
|
||||
public IntegerProperty getFrameRateProperty() {
|
||||
return visualiserRaceService.getFrameRateProperty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Terminates the server connection and race service.
|
||||
*/
|
||||
public void terminate() {
|
||||
this.serverConnectionThread.interrupt();
|
||||
|
||||
this.visualiserRaceServiceThread.interrupt();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package visualiser.model;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import mock.model.commandFactory.CompositeCommand;
|
||||
import shared.model.FrameRateTracker;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
|
||||
|
||||
/**
|
||||
* Handles updating a {@link VisualiserRaceState} with incoming commands.
|
||||
*/
|
||||
public class VisualiserRaceService implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* The race state to update.
|
||||
*/
|
||||
private VisualiserRaceState visualiserRaceState;
|
||||
|
||||
|
||||
/**
|
||||
* A composite commands to execute to update the race.
|
||||
*/
|
||||
private CompositeCommand raceCommands;
|
||||
|
||||
|
||||
/**
|
||||
* Used to track the framerate of the "simulation".
|
||||
*/
|
||||
private FrameRateTracker frameRateTracker;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a visualiser race which models a yacht race, and is modified by CompositeCommand.
|
||||
* @param raceCommands A composite commands to execute to update the race.
|
||||
* @param visualiserRaceState The race state to update.
|
||||
*/
|
||||
public VisualiserRaceService(CompositeCommand raceCommands, VisualiserRaceState visualiserRaceState) {
|
||||
this.raceCommands = raceCommands;
|
||||
this.visualiserRaceState = visualiserRaceState;
|
||||
|
||||
this.frameRateTracker = new FrameRateTracker();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the CompositeCommand executed by the race.
|
||||
* @return CompositeCommand executed by race.
|
||||
*/
|
||||
public CompositeCommand getRaceCommands() {
|
||||
return raceCommands;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
long previousFrameTime = System.currentTimeMillis();
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
long currentFrameTime = System.currentTimeMillis();
|
||||
|
||||
waitForFramePeriod(previousFrameTime, currentFrameTime, 16);
|
||||
|
||||
previousFrameTime = currentFrameTime;
|
||||
|
||||
|
||||
raceCommands.execute();
|
||||
|
||||
}
|
||||
|
||||
frameRateTracker.stop();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the framerate property of the race.
|
||||
* @return Framerate property of race.
|
||||
*/
|
||||
public IntegerProperty getFrameRateProperty() {
|
||||
return frameRateTracker.fpsProperty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
package visualiser.model;
|
||||
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.paint.Color;
|
||||
import network.Messages.Enums.BoatStatusEnum;
|
||||
import shared.dataInput.BoatDataSource;
|
||||
import shared.dataInput.RaceDataSource;
|
||||
import shared.dataInput.RegattaDataSource;
|
||||
import shared.exceptions.BoatNotFoundException;
|
||||
import shared.exceptions.MarkNotFoundException;
|
||||
import shared.model.*;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
* This class contains all of the state of a race on the client (visualiser) side.
|
||||
*/
|
||||
public class VisualiserRaceState extends RaceState {
|
||||
|
||||
|
||||
/**
|
||||
* A list of boats in the race.
|
||||
*/
|
||||
private ObservableList<VisualiserBoat> boats;
|
||||
|
||||
/**
|
||||
* The source ID of the boat assigned to the player.
|
||||
* 0 if no boat has been assigned.
|
||||
*/
|
||||
private int playerBoatID;
|
||||
|
||||
|
||||
/**
|
||||
* Maps between a Leg to a list of boats, in the order that they finished the leg.
|
||||
* Used by the Sparkline to ensure it has correct information.
|
||||
* TODO BUG: if we receive a race.xml file during the race, then we need to add/remove legs to this, without losing information.
|
||||
*/
|
||||
private Map<Leg, List<VisualiserBoat>> legCompletionOrder;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor.
|
||||
*/
|
||||
private List<Color> unassignedColors = new ArrayList<>(Arrays.asList(
|
||||
Color.BLUEVIOLET,
|
||||
Color.BLACK,
|
||||
Color.RED,
|
||||
Color.ORANGE,
|
||||
Color.DARKOLIVEGREEN,
|
||||
Color.LIMEGREEN,
|
||||
Color.PURPLE,
|
||||
Color.DARKGRAY,
|
||||
Color.YELLOW
|
||||
//TODO may need to add more colors.
|
||||
));
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a visualiser race which models a yacht race.
|
||||
* @param raceDataSource The raceDataSource to initialise with.
|
||||
* @param regattaDataSource The regattaDataSource to initialise with.
|
||||
* @param boatDataSource The boatDataSource to initialise with.
|
||||
*/
|
||||
public VisualiserRaceState(RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, BoatDataSource boatDataSource) {
|
||||
|
||||
this.boats = FXCollections.observableArrayList();
|
||||
|
||||
this.playerBoatID = 0;
|
||||
|
||||
this.legCompletionOrder = new HashMap<>();
|
||||
|
||||
|
||||
setRaceDataSource(raceDataSource);
|
||||
setRegattaDataSource(regattaDataSource);
|
||||
setBoatDataSource(boatDataSource);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the race data source for this race to a new RaceDataSource.
|
||||
* Uses the boundary and legs specified by the new RaceDataSource.
|
||||
* @param raceDataSource The new RaceDataSource to use.
|
||||
*/
|
||||
public void setRaceDataSource(RaceDataSource raceDataSource) {
|
||||
super.setRaceDataSource(raceDataSource);
|
||||
|
||||
if (getBoatDataSource() != null) {
|
||||
this.generateVisualiserBoats(this.boats, getBoatDataSource().getBoats(), raceDataSource.getParticipants(), unassignedColors);
|
||||
}
|
||||
|
||||
initialiseLegCompletionOrder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the boat data source for this race to a new BoatDataSource.
|
||||
* Uses the marker boats specified by the new BoatDataSource.
|
||||
* @param boatDataSource The new BoatDataSource to use.
|
||||
*/
|
||||
public void setBoatDataSource(BoatDataSource boatDataSource) {
|
||||
super.setBoatDataSource(boatDataSource);
|
||||
|
||||
if (getRaceDataSource() != null) {
|
||||
this.generateVisualiserBoats(this.boats, boatDataSource.getBoats(), getRaceDataSource().getParticipants(), unassignedColors);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the regatta data source for this race to a new RegattaDataSource.
|
||||
* @param regattaDataSource The new RegattaDataSource to use.
|
||||
*/
|
||||
public void setRegattaDataSource(RegattaDataSource regattaDataSource) {
|
||||
super.setRegattaDataSource(regattaDataSource);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialises the {@link #legCompletionOrder} map.
|
||||
*/
|
||||
public void initialiseLegCompletionOrder() {
|
||||
//Initialise the leg completion order map.
|
||||
for (Leg leg : getLegs()) {
|
||||
this.legCompletionOrder.put(leg, new ArrayList<>(this.boats.size()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generates a list of VisualiserBoats given a list of Boats, and a list of participating boats.
|
||||
* This will add VisualiserBoats for newly participating sourceID, and remove VisualiserBoats for any participating sourceIDs that have been removed.
|
||||
*
|
||||
* @param existingBoats The visualiser boats that already exist in the race. This will be populated when we receive a new race.xml or boats.xml.
|
||||
* @param boats The map of {@link Boat}s describing boats that are potentially in the race. Maps boat sourceID to boat.
|
||||
* @param sourceIDs The list of boat sourceIDs describing which specific boats are actually participating.
|
||||
* @param colors The list of unassignedColors to be used for the boats.
|
||||
*/
|
||||
private void generateVisualiserBoats(ObservableList<VisualiserBoat> existingBoats, Map<Integer, Boat> boats, List<Integer> sourceIDs, List<Color> colors) {
|
||||
|
||||
//Remove any VisualiserBoats that are no longer participating.
|
||||
for (VisualiserBoat boat : new ArrayList<>(existingBoats)) {
|
||||
|
||||
//Boat no longer is participating.
|
||||
if (!sourceIDs.contains(boat.getSourceID())) {
|
||||
//Return their colors to the color list.
|
||||
colors.add(boat.getColor());
|
||||
|
||||
//Remove boat.
|
||||
existingBoats.remove(boat);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Get source IDs of already existing boats.
|
||||
List<Integer> existingBoatIDs = new ArrayList<>();
|
||||
for (VisualiserBoat boat : existingBoats) {
|
||||
existingBoatIDs.add(boat.getSourceID());
|
||||
}
|
||||
|
||||
//Get source IDs of only newly participating boats.
|
||||
List<Integer> newBoatIDs = new ArrayList<>(sourceIDs);
|
||||
newBoatIDs.removeAll(existingBoatIDs);
|
||||
|
||||
//Create VisualiserBoat for newly participating boats.
|
||||
for (Integer sourceID : newBoatIDs) {
|
||||
|
||||
if (boats.containsKey(sourceID)) {
|
||||
|
||||
VisualiserBoat boat = new VisualiserBoat(
|
||||
boats.get(sourceID),
|
||||
colors.remove(colors.size() - 1));//TODO potential bug: not enough colors for boats.
|
||||
|
||||
boat.setCurrentLeg(getLegs().get(0));
|
||||
|
||||
existingBoats.add(boat);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setPlayerBoat();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the boat the player has been assigned to as belonging to them.
|
||||
*/
|
||||
private void setPlayerBoat() {
|
||||
|
||||
if (getPlayerBoatID() != 0) {
|
||||
|
||||
for (VisualiserBoat boat : new ArrayList<>(getBoats())) {
|
||||
|
||||
if (boat.getSourceID() == getPlayerBoatID()) {
|
||||
boat.setClientBoat(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialise the boats in the race.
|
||||
* This sets their current leg.
|
||||
*/
|
||||
@Override
|
||||
protected void initialiseBoats() {
|
||||
|
||||
Leg startingLeg = getLegs().get(0);
|
||||
|
||||
for (VisualiserBoat boat : boats) {
|
||||
|
||||
boat.setCurrentLeg(startingLeg);
|
||||
boat.setTimeAtLastMark(getRaceClock().getCurrentTime());
|
||||
boat.setCurrentPosition(new GPSCoordinate(0, 0));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Update position of boats in race (e.g, 5th), no position if on starting leg or DNF.
|
||||
* @param boats The list of boats to update.
|
||||
*/
|
||||
public void updateBoatPositions(List<VisualiserBoat> boats) {
|
||||
|
||||
//Sort boats.
|
||||
sortBoatsByPosition(boats);
|
||||
|
||||
//Assign new positions.
|
||||
for (int i = 0; i < boats.size(); i++) {
|
||||
VisualiserBoat boat = boats.get(i);
|
||||
|
||||
|
||||
if ((boat.getStatus() == BoatStatusEnum.DNF) || (boat.getStatus() == BoatStatusEnum.PRESTART) || (boat.getCurrentLeg().getLegNumber() < 0)) {
|
||||
|
||||
boat.setPosition("-");
|
||||
|
||||
} else {
|
||||
boat.setPosition(Integer.toString(i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sorts the list of boats by their position within the race.
|
||||
* @param boats The list of boats in the race.
|
||||
*/
|
||||
private void sortBoatsByPosition(List<VisualiserBoat> boats) {
|
||||
|
||||
boats.sort((a, b) -> {
|
||||
//Get the difference in leg numbers.
|
||||
int legNumberDelta = b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber();
|
||||
|
||||
//If they're on the same leg, we need to compare time to finish leg.
|
||||
if (legNumberDelta == 0) {
|
||||
|
||||
//These are potentially null until we receive our first RaceStatus containing BoatStatuses.
|
||||
if ((a.getEstimatedTimeAtNextMark() != null) && (b.getEstimatedTimeAtNextMark() != null)) {
|
||||
|
||||
return (int) Duration.between(
|
||||
b.getEstimatedTimeAtNextMark(),
|
||||
a.getEstimatedTimeAtNextMark() ).toMillis();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return legNumberDelta;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the boats participating in the race.
|
||||
* @return List of boats participating in the race.
|
||||
*/
|
||||
public ObservableList<VisualiserBoat> getBoats() {
|
||||
return boats;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a boat by sourceID.
|
||||
* @param sourceID The source ID the boat.
|
||||
* @return The boat.
|
||||
* @throws BoatNotFoundException Thrown if there is no boat with the specified sourceID.
|
||||
*/
|
||||
public VisualiserBoat getBoat(int sourceID) throws BoatNotFoundException {
|
||||
|
||||
for (VisualiserBoat boat : boats) {
|
||||
|
||||
if (boat.getSourceID() == sourceID) {
|
||||
return boat;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new BoatNotFoundException("Boat with sourceID: " + sourceID + " was not found.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not there exists a {@link VisualiserBoat} with the given source ID.
|
||||
* @param sourceID SourceID of VisualiserBoat.
|
||||
* @return True if VisualiserBoat exists, false otherwise.
|
||||
*/
|
||||
public boolean isVisualiserBoat(int sourceID) {
|
||||
|
||||
try {
|
||||
getBoat(sourceID);
|
||||
return true;
|
||||
} catch (BoatNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a mark by sourceID.
|
||||
* @param sourceID The source ID the mark.
|
||||
* @return The mark.
|
||||
* @throws MarkNotFoundException Thrown if there is no mark with the specified sourceID.
|
||||
*/
|
||||
public Mark getMark(int sourceID) throws MarkNotFoundException {
|
||||
|
||||
for (Mark mark : getMarks()) {
|
||||
|
||||
if (mark.getSourceID() == sourceID) {
|
||||
return mark;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new MarkNotFoundException("Mark with sourceID: " + sourceID + " was not found.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not there exists a {@link Mark} with the given source ID.
|
||||
* @param sourceID SourceID of mark.
|
||||
* @return True if mark exists, false otherwise.
|
||||
*/
|
||||
public boolean isMark(int sourceID) {
|
||||
|
||||
try {
|
||||
getMark(sourceID);
|
||||
return true;
|
||||
} catch (MarkNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the order in which boats completed each leg. Maps the leg to a list of boats, ordered by the order in which they finished the leg.
|
||||
* @return Leg completion order for each leg.
|
||||
*/
|
||||
public Map<Leg, List<VisualiserBoat>> getLegCompletionOrder() {
|
||||
return legCompletionOrder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the source ID of the player's boat. 0 if not assigned.
|
||||
* @return Players boat source ID.
|
||||
*/
|
||||
public int getPlayerBoatID() {
|
||||
return playerBoatID;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the source ID of the player's boat. 0 if not assigned.
|
||||
* @param playerBoatID Players boat source ID.
|
||||
*/
|
||||
public void setPlayerBoatID(int playerBoatID) {
|
||||
this.playerBoatID = playerBoatID;
|
||||
setPlayerBoat();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user