From 89ef6e5277adc02799d3b370fcc9ca7b347db054 Mon Sep 17 00:00:00 2001 From: Calum Date: Sun, 14 May 2017 17:24:15 +1200 Subject: [PATCH 01/17] Wake calculation now changed to be based off of the separation between wakes. This allows wakes to auto correct their position better and stops the system reliance on "realistic data". Wakes have several options for behaviour until the ideal settings are decided upon. Note that MarkGroup position updating is currently disabled. #implement #refactor #issue[1] #story[923] --- src/main/java/seng302/App.java | 3 +- src/main/java/seng302/models/BoatGroup.java | 24 ++- src/main/java/seng302/models/Wake.java | 143 +++++++++++++----- .../java/seng302/models/mark/MarkGroup.java | 72 ++++----- 4 files changed, 158 insertions(+), 84 deletions(-) diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 1a400afd..4aa14563 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -62,7 +62,8 @@ public class App extends Application } //Change the StreamReceiver in this else block to change the default data source. else{ - sr = new StreamReceiver("localhost", 4949, "RaceStream"); +// sr = new StreamReceiver("localhost", 4949, "RaceStream"); + sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); } sr.start(); diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 57dd48db..70b979ff 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -204,25 +204,23 @@ public class BoatGroup extends RaceObject{ double dx = newXValue - boatPoly.getLayoutX(); double dy = newYValue - boatPoly.getLayoutY(); //Check movement is reasonable. Assumes a 1000 * 1000 canvas - if (Math.abs(dx) > 50 || Math.abs(dy) > 50) { - dx = 0; - dy = 0; - moveTo(newXValue, newYValue); - } +// if (Math.abs(dx) > 50 || Math.abs(dy) > 50) { +// dx = 0; +// dy = 0; +// moveTo(newXValue, newYValue); +// } pixelVelocityX = dx / expectedUpdateInterval; pixelVelocityY = dy / expectedUpdateInterval; rotationalGoal = rotation; calculateRotationalVelocity(); - - if (wakeGenerationDelay > 0) { - wake.rotate(rotationalGoal); - rotateTo(rotationalGoal); //Need to test with this removed. + if (Math.abs(rotationalVelocity) > 0.075) { + System.out.println("rotationalVelocity = " + rotationalVelocity); rotationalVelocity = 0; - wakeGenerationDelay--; - } else { - wake.setRotationalVelocity(rotationalVelocity, rotationalGoal, boat.getVelocity()); + rotateTo(rotationalGoal); + wake.rotate(rotationalGoal); } + wake.setRotationalVelocity(rotationalVelocity, boat.getVelocity()); velocityObject.setText(String.format("%.2f m/s", boat.getVelocity())); } else { setToInitialLocation = true; @@ -347,7 +345,7 @@ public class BoatGroup extends RaceObject{ App.start() -> Controller.setContentPane -> RaceViewController -> CanvasController */ this.stage = stage; - this.stage.iconifiedProperty().addListener(e -> { + this.stage.iconifiedProperty().addListener( e -> { isMaximized = !stage.isIconified(); if (!lineStorage.isEmpty()) { lineGroup.getChildren().addAll(lineStorage); diff --git a/src/main/java/seng302/models/Wake.java b/src/main/java/seng302/models/Wake.java index 55d4381c..9f6ef621 100644 --- a/src/main/java/seng302/models/Wake.java +++ b/src/main/java/seng302/models/Wake.java @@ -4,6 +4,7 @@ import javafx.scene.Group; import javafx.scene.paint.Color; import javafx.scene.shape.Arc; import javafx.scene.shape.ArcType; +import javafx.scene.shape.StrokeLineCap; import javafx.scene.transform.Rotate; /** @@ -15,13 +16,22 @@ import javafx.scene.transform.Rotate; */ class Wake extends Group { - private int numWakes = 5; - private double[] velocities = new double[13]; + //Wake Settings. Should probably be hard coded in when the final values are decided upon. + private enum functionType {LINEAR, LOGARITHMIC, POWER, ROOT, POWOUT_LOGIN} + private functionType wakeFunction = functionType.LOGARITHMIC; + private ArcType arcType = ArcType.OPEN; + private int numWakes = 10; + private double offSet = 0; + private final double MAX_DIFF = 75.0; + private final int UNIFICATION_SPEED = 500; + private final int POWER = 2; + private Arc[] arcs = new Arc[numWakes]; + private double[] rotationalVelocities = new double[numWakes]; private double[] rotations = new double[numWakes]; - private int[] velocityIndices = new int[numWakes]; - private double sum = 0; - private static double max; + private double baseRad; + private boolean spawnNewWake = false; + private int count = 10; /** * Create a wake at the given location. @@ -34,11 +44,22 @@ class Wake extends Group { Arc arc; for (int i = 0; i < numWakes; i++) { //Default triangle is -110 deg out of phase with a default wake and has angle of 40 deg. - arc = new Arc(0,0,0,0,-110,40); - //Opacity increases from 0.5 -> 0 evenly over the 5 wake arcs. - arc.setFill(new Color(0.18, 0.7, 1.0, 1.0 + -0.175 * i)); + arc = new Arc(0, 0, 0, 0, -110, 40); + + if (arcType == ArcType.ROUND) { + arc.setFill(new Color(0.18, 0.7, 1.0, 0.4 + (-0.35 / numWakes * i))); arc.setType(ArcType.ROUND); - arcs[i] = arc; + baseRad = 10; + + } else if (arcType == ArcType.OPEN) { + arc.setType(ArcType.OPEN); + arc.setStroke(new Color(0.18, 0.7, 1.0, 1.0 + (-0.99 / numWakes * i))); + arc.setStrokeWidth(3.0); + arc.setStrokeLineCap(StrokeLineCap.ROUND); + arc.setFill(new Color(0.0, 0.0, 0.0, 0.0)); + baseRad = (20 / numWakes); + arcs[i] = arc; + } } super.getChildren().addAll(arcs); } @@ -47,38 +68,91 @@ class Wake extends Group { * Sets the rotationalVelocity of each arc. Each arc is 3 velocities behind the next smallest arc. The smallest uses * the latest given velocity. * @param rotationalVelocity The rotationalVelocity the wake should move at. - * @param rotationGoal Where the wake will rotate to if the wake is calculated to be on a straight section. This is - * used to prevent desynchronisation with the Boat polygon. * @param velocity The real world velocity of the boat in m/s. */ - void setRotationalVelocity (double rotationalVelocity, double rotationGoal, double velocity) { - sum -= Math.abs(velocities[(velocityIndices[0] + 10) % 13]); - sum += Math.abs(rotationalVelocity); - max = Math.max(max, rotationalVelocity); - if (sum < (max / 3)) - rotate (rotationGoal); //In relatively straight segments the wake snaps to match the boats current position. - //This stops the wake from eventually becoming out of sync with the boat. - //This accounts for rogue rotations that are greater than what would be realistic. Value is kinda rough. - //Basically just for our internal mock. - if (Math.abs(rotationalVelocity) > 0.05) { - rotationalVelocity = 0; - rotate(rotationGoal); + void setRotationalVelocity (double rotationalVelocity, double velocity) { + rotationalVelocities[0] = rotationalVelocity; + for (int i = 1; i < numWakes; i++) { + double difference = Math.atan2( + Math.sin( + Math.toRadians( + rotations[i - 1] - rotations[i] + ) + ), + Math.cos( + Math.toRadians( + rotations[i - 1] - rotations[i] + ) + ) + ); + difference = Math.toDegrees(difference); + + if (wakeFunction == functionType.LOGARITHMIC) { + if (rotationalVelocities[i-1] < 0.01 && rotationalVelocities[i-1] > -0.01) { + rotationalVelocities[i] = (MAX_DIFF / numWakes) / UNIFICATION_SPEED * Math.log(Math.abs(difference) + 1) / Math.log(MAX_DIFF / numWakes) * 1.5; + if (difference < 0) + { + rotationalVelocities[i] = -rotationalVelocities[i]; + } + } else { + rotationalVelocities[i] = rotationalVelocities[i-1] * Math.log(Math.abs(difference) + 1) / Math.log(MAX_DIFF / numWakes); + } + + } else if (wakeFunction == functionType.LINEAR) { + if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) { + rotationalVelocities[i] = difference / UNIFICATION_SPEED * 2; + } else { + if (difference < (MAX_DIFF / numWakes)) + rotationalVelocities[i] = rotationalVelocities[i - 1] * difference / (MAX_DIFF / numWakes); + else + rotationalVelocities[i] = rotationalVelocities[i - 1]; + } + } else if (wakeFunction == functionType.POWER) { + if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) { + rotationalVelocities[i] = difference / UNIFICATION_SPEED * Math.pow(difference, POWER) / Math.pow((MAX_DIFF / numWakes), POWER); + } else { + if (difference < (MAX_DIFF / numWakes)) + rotationalVelocities[i] = rotationalVelocities[i - 1] * Math.pow(difference, POWER) / Math.pow((MAX_DIFF / numWakes), POWER); + else + rotationalVelocities[i] = rotationalVelocities[i - 1]; + } + } else if (wakeFunction == functionType.ROOT) { + if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) { + rotationalVelocities[i] = (MAX_DIFF / numWakes) / UNIFICATION_SPEED * Math.sqrt(Math.abs(difference)) / Math.sqrt(MAX_DIFF / numWakes); + } else { + if (difference < (MAX_DIFF / numWakes)) + rotationalVelocities[i] = rotationalVelocities[i - 1] * Math.sqrt(Math.abs(difference)) / Math.sqrt(MAX_DIFF / numWakes); + else + rotationalVelocities[i] = rotationalVelocities[i - 1]; + } + if (difference < 0) + rotationalVelocities[i] = -rotationalVelocities[i]; + } else if (wakeFunction == functionType.POWOUT_LOGIN) { + if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) { + rotationalVelocities[i] = difference / UNIFICATION_SPEED * Math.log(Math.abs(difference) + 1) / Math.log(MAX_DIFF / numWakes); + } else { + if (difference < (MAX_DIFF / numWakes)) + rotationalVelocities[i] = rotationalVelocities[i - 1] * Math.pow(difference, POWER) / Math.pow((MAX_DIFF / numWakes), POWER); + else + rotationalVelocities[i] = rotationalVelocities[i - 1]; + } + } + } - //Update the index of the array of recent velocities that each wake uses. Each wake is 3 velocities behind the - //next smallest wake. - velocityIndices[0] = (13 + (velocityIndices[0] - 1) % 13) % 13; - velocities[velocityIndices[0]] = rotationalVelocity; - for (int i = 1; i < numWakes; i++) - velocityIndices[i] = (velocityIndices[0] + 3 * i) % 13; //Scale wakes based on velocity. - double baseRad = 20; - double rad; +// if (count-- == 0) +// { +// count = 10; +// offSet = 0; +// } else { +// offSet += baseRad / 5; +// } + double rad = baseRad + velocity + offSet; for (Arc arc :arcs) { - rad = baseRad + velocity; arc.setRadiusX(rad); arc.setRadiusY(rad); - baseRad += 5 + (velocity / 2); + rad += (20 / numWakes) + (velocity / 2); } } @@ -88,7 +162,7 @@ class Wake extends Group { */ void updatePosition (long timeInterval) { for (int i = 0; i < numWakes; i++) { - rotations[i] = rotations[i] + velocities[velocityIndices[i]] * timeInterval; + rotations[i] = rotations[i] + rotationalVelocities[i] * timeInterval; arcs[i].getTransforms().setAll(new Rotate(rotations[i])); } } @@ -100,6 +174,7 @@ class Wake extends Group { void rotate (double rotation) { for (int i = 0; i < arcs.length; i++) { rotations[i] = rotation; + rotationalVelocities[i] = 0; arcs[i].getTransforms().setAll(new Rotate(rotation)); } } diff --git a/src/main/java/seng302/models/mark/MarkGroup.java b/src/main/java/seng302/models/mark/MarkGroup.java index 29931e01..a0f12dcc 100644 --- a/src/main/java/seng302/models/mark/MarkGroup.java +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -126,42 +126,42 @@ public class MarkGroup extends RaceObject { } public void updatePosition (long timeInterval) { - Circle markCircle = (Circle) super.getChildren().get(0); - - if (nodePixelVelocitiesX[0] > 0 && markCircle.getCenterX() > nodeDestinations[0].getX() || - nodePixelVelocitiesX[0] < 0 && markCircle.getCenterX() < nodeDestinations[0].getY()) - nodePixelVelocitiesX[0] = 0; - else if (nodePixelVelocitiesX[0] != 0) - markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[0] * timeInterval); - - if (nodePixelVelocitiesY[0] > 0 && markCircle.getCenterY() > nodeDestinations[0].getY() || - nodePixelVelocitiesY[0] < 0 && markCircle.getCenterY() < nodeDestinations[0].getY()) - nodePixelVelocitiesY[0] = 0; - else if (nodePixelVelocitiesY[0] != 0) - markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[0] * timeInterval); - - if (mainMark.getMarkType() != MarkType.SINGLE_MARK) { - - Line line = (Line) super.getChildren().get(2); - line.setStartX(markCircle.getCenterX()); - line.setStartY(markCircle.getCenterY()); - - markCircle = (Circle) super.getChildren().get(1); - - if (nodePixelVelocitiesX[1] > 0 && markCircle.getCenterX() >= nodeDestinations[1].getX() || - nodePixelVelocitiesX[1] < 0 && markCircle.getCenterX() <= nodeDestinations[1].getX()) - nodePixelVelocitiesX[1] = 0; - else if (nodePixelVelocitiesX[1] != 0) - markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[1] * timeInterval); - - if (nodePixelVelocitiesY[1] > 0 && markCircle.getCenterY() > nodeDestinations[1].getY() || - nodePixelVelocitiesY[1] < 0 && markCircle.getCenterY() < nodeDestinations[1].getY()) - nodePixelVelocitiesY[1] = 0; - else if (nodePixelVelocitiesY[1] != 0) - markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[1] * timeInterval); - line.setEndX(markCircle.getCenterX()); - line.setEndY(markCircle.getCenterY()); - } +// Circle markCircle = (Circle) super.getChildren().get(0); +// +// if (nodePixelVelocitiesX[0] > 0 && markCircle.getCenterX() > nodeDestinations[0].getX() || +// nodePixelVelocitiesX[0] < 0 && markCircle.getCenterX() < nodeDestinations[0].getY()) +// nodePixelVelocitiesX[0] = 0; +// else if (nodePixelVelocitiesX[0] != 0) +// markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[0] * timeInterval); +// +// if (nodePixelVelocitiesY[0] > 0 && markCircle.getCenterY() > nodeDestinations[0].getY() || +// nodePixelVelocitiesY[0] < 0 && markCircle.getCenterY() < nodeDestinations[0].getY()) +// nodePixelVelocitiesY[0] = 0; +// else if (nodePixelVelocitiesY[0] != 0) +// markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[0] * timeInterval); +// +// if (mainMark.getMarkType() != MarkType.SINGLE_MARK) { +// +// Line line = (Line) super.getChildren().get(2); +// line.setStartX(markCircle.getCenterX()); +// line.setStartY(markCircle.getCenterY()); +// +// markCircle = (Circle) super.getChildren().get(1); +// +// if (nodePixelVelocitiesX[1] > 0 && markCircle.getCenterX() >= nodeDestinations[1].getX() || +// nodePixelVelocitiesX[1] < 0 && markCircle.getCenterX() <= nodeDestinations[1].getX()) +// nodePixelVelocitiesX[1] = 0; +// else if (nodePixelVelocitiesX[1] != 0) +// markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[1] * timeInterval); +// +// if (nodePixelVelocitiesY[1] > 0 && markCircle.getCenterY() > nodeDestinations[1].getY() || +// nodePixelVelocitiesY[1] < 0 && markCircle.getCenterY() < nodeDestinations[1].getY()) +// nodePixelVelocitiesY[1] = 0; +// else if (nodePixelVelocitiesY[1] != 0) +// markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[1] * timeInterval); +// line.setEndX(markCircle.getCenterX()); +// line.setEndY(markCircle.getCenterY()); +// } } public void moveGroupBy (double x, double y, double rotation) { From 764ae37ce47feca584f6cfcec3b56db7577337d7 Mon Sep 17 00:00:00 2001 From: William Muir Date: Mon, 15 May 2017 14:09:09 +1200 Subject: [PATCH 02/17] Gave the boatgroups a selection attribute, allowing them to be highlighted upon clicking Boats can be clicked on canvas or from selection drop down on the side #story[955] --- .../controllers/RaceViewController.java | 37 +++++++++++++++++++ src/main/java/seng302/models/BoatGroup.java | 32 +++++++++++++++- src/main/java/seng302/models/Yacht.java | 5 +++ src/main/resources/views/RaceView.fxml | 2 + 4 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 24ff0163..438f972b 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -5,11 +5,15 @@ import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableArray; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; import javafx.scene.control.Slider; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Pane; @@ -50,6 +54,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel @FXML private Button selectAnnotationBtn; @FXML + private ComboBox boatSelectionComboBox; + @FXML private CanvasController includedCanvasController; private ArrayList startingBoats = new ArrayList<>(); @@ -79,6 +85,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel initializeSettings(); initialiseWindDirection(); initialisePositionVBox(); + initialiseBoatSelectionComboBox(); //set wind direction!!!!!!! can't find another place to put my code --haoming // double windDirection = new ConfigParser("/config/config.xml").getWindDirection(); // windDirectionText.setText(String.format("%.1f°", windDirection)); @@ -220,6 +227,16 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } + private void initialiseBoatSelectionComboBox() { + + ObservableList observableBoats = FXCollections.observableArrayList(startingBoats); + boatSelectionComboBox.setItems(observableBoats); + boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { + Yacht thisYacht = (Yacht) newValue; + setSelectedBoat(thisYacht); + }); + } + /** * Generates time line for each boat, and stores time time into timelineInfos hash map */ @@ -511,6 +528,26 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } } + + /** + * Sets all the annotations of the selected boat to be visible and all others to be hidden + * @param yacht The yacht for which we want to view all annotations + */ + private void setSelectedBoat(Yacht yacht) { + for (RaceObject ro : includedCanvasController.getRaceObjects()) { + if (ro instanceof BoatGroup) { + BoatGroup bg = (BoatGroup) ro; + //We need to iterate over all race groups to get the matching boat group belonging to this boat if we + //are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup. + if (bg.getBoat().getHullID().equals(yacht.getHullID())) { + bg.setIsSelected(true); + } else { + bg.setIsSelected(false); + } + } + } + } + void setStage (Stage stage) { this.stage = stage; } diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 57dd48db..a2831c52 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -1,13 +1,16 @@ package seng302.models; +import javafx.event.EventHandler; import javafx.geometry.Point2D; import javafx.scene.Group; +import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.shape.Line; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; import javafx.scene.transform.Rotate; import javafx.stage.Stage; +import seng302.controllers.RaceViewController; import java.util.ArrayList; import java.util.List; @@ -39,6 +42,7 @@ public class BoatGroup extends RaceObject{ private Text teamNameObject; private Text velocityObject; private Wake wake; + private boolean isSelected = false; //Handles boat moving when connecting to a stream private boolean setToInitialLocation = false; private boolean destinationSet; @@ -80,6 +84,9 @@ public class BoatGroup extends RaceObject{ private void initChildren (Color color, double... points) { boatPoly = new Polygon(points); boatPoly.setFill(color); + boatPoly.setOnMouseEntered(event -> boatPoly.setFill(Color.FLORALWHITE)); + boatPoly.setOnMouseExited(event -> boatPoly.setFill(color)); + boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected)); //Toggle the selection of the boat teamNameObject = new Text(boat.getShortName()); velocityObject = new Text(String.valueOf(boat.getVelocity())); @@ -176,7 +183,7 @@ public class BoatGroup extends RaceObject{ boatPoly.getLayoutY() ); l.getStrokeDashArray().setAll(3d, 7d); - l.setStroke(boatPoly.getFill()); + l.setStroke(boat.getColour()); lineGroup.getChildren().add(l); } if (destinationSet) { //Only begin drawing after the first destination is set @@ -279,6 +286,10 @@ public class BoatGroup extends RaceObject{ wake.rotate(rotationalGoal); } + public void paintBoat (Color color) { + boatPoly.setFill(color); + } + public void setTeamNameObjectVisible(Boolean visible) { teamNameObject.setVisible(visible); } @@ -299,6 +310,20 @@ public class BoatGroup extends RaceObject{ return boat; } + /** + * This function sets the boats isSelected property AS WELL as actually acting upon the value of that selection. + * (Painting or not painting annotations) + * @param isSelected A Boolean indicating whether or not the boat is selected + */ + public void setIsSelected(Boolean isSelected) { + this.isSelected = isSelected; + setTeamNameObjectVisible(isSelected); + setVelocityObjectVisible(isSelected); + setLineGroupVisible(isSelected); + setWakeVisible(isSelected); + paintBoat((isSelected) ? Color.WHITE : boat.getColour()); + } + /** * Returns true if this BoatGroup contains at least one of the given IDs. * @@ -355,4 +380,9 @@ public class BoatGroup extends RaceObject{ } }); } + + @Override + public String toString() { + return boat.toString(); + } } diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/models/Yacht.java index 68970268..64509882 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/models/Yacht.java @@ -164,4 +164,9 @@ public class Yacht { public void setMarkLastPast(Integer markLastPast) { this.markLastPast = markLastPast; } + + @Override + public String toString() { + return boatName; + } } diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml index 32d6f710..3dc8bf9c 100644 --- a/src/main/resources/views/RaceView.fxml +++ b/src/main/resources/views/RaceView.fxml @@ -59,6 +59,8 @@