diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf33..00000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index f86ff1a3..b22f7658 100644 --- a/pom.xml +++ b/pom.xml @@ -28,11 +28,20 @@ mockito-all 1.9.5 + com.github.bfsmith geotimezone 1.0.3 + + + org.testng + testng + 6.11 + test + + diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 72e98838..cf9d3ed2 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -6,10 +6,6 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.layout.BorderPane; import javafx.stage.Stage; -import seng302.Controllers.Controller; -import seng302.Controllers.MainController; - -import java.io.InputStream; public class App extends Application { Stage primaryStage; @@ -25,54 +21,62 @@ public class App extends Application { launch(args); } - /** - * Loads and sets up the GUI elements - * - * @param primaryStage Base for all scenes - * @throws Exception Error in initialising programme - */ - @Override - public void start(Stage primaryStage) throws Exception { - this.primaryStage = primaryStage; - primaryStage.minHeightProperty().setValue(600); - primaryStage.minWidthProperty().setValue(780); - //load the first container - try { - FXMLLoader loader = new FXMLLoader(); - InputStream in = getClass().getClassLoader().getResourceAsStream("scenes/mainpane.fxml"); - mainContainer = (BorderPane) loader.load(in); - mainScene = new Scene(mainContainer, 1200, 800); - primaryStage.setScene(mainScene); - primaryStage.sizeToScene(); - MainController mainController = (MainController) loader.getController(); - mainController.setParent(this); - in.close(); - //add the center - loadPane("racepane.fxml"); - } catch (Exception e) { - e.printStackTrace(); - } - primaryStage.show(); + public void start(Stage stage) throws Exception { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/scenes/main.fxml")); + Parent root = loader.load(); + Scene scene = new Scene(root, 1200, 800); + stage.setScene(scene); + stage.show(); } - /** - * Loads panes for use in the GUI - * - * @param fxmlName name of resource fxml file - * @throws Exception critical error in loading file - */ - public void loadPane(String fxmlName) throws Exception { - FXMLLoader loader = new FXMLLoader(); - InputStream in = getClass().getClassLoader().getResourceAsStream("scenes/" + fxmlName); - Parent page; - try { - page = (Parent) loader.load(in); - } finally { - in.close(); - } - mainContainer.getChildren().remove(mainContainer.getCenter()); - mainContainer.setCenter(page); - Controller controller = (Controller) loader.getController(); - controller.setParent(this); - } +// /** +// * Loads and sets up the GUI elements +// * +// * @param primaryStage Base for all scenes +// * @throws Exception Error in initialising programme +// */ +// @Override +// public void start(Stage primaryStage) throws Exception { +// this.primaryStage = primaryStage; +// primaryStage.minHeightProperty().setValue(600); +// primaryStage.minWidthProperty().setValue(780); +// //load the first container +// try { +// FXMLLoader loader = new FXMLLoader(); +// InputStream in = getClass().getClassLoader().getResourceAsStream("scenes/main.fxml"); +// mainContainer = (BorderPane) loader.load(in); +// mainScene = new Scene(mainContainer, 1200, 800); +// primaryStage.setScene(mainScene); +// primaryStage.sizeToScene(); +// MainController mainController = (MainController) loader.getController(); +// mainController.setParent(this); +// in.close(); +// //add the center +// loadPane("race.fxml"); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// primaryStage.show(); +// } +// +// /** +// * Loads panes for use in the GUI +// * +// * @param fxmlName name of resource fxml file +// * @throws Exception critical error in loading file +// */ +// public void loadPane(String fxmlName) throws Exception { +// FXMLLoader loader = new FXMLLoader(); +// InputStream in = getClass().getClassLoader().getResourceAsStream("scenes/" + fxmlName); +// Parent page; +// try { +// page = (Parent) loader.load(in); +// } finally { +// in.close(); +// } +// mainContainer.getChildren().remove(mainContainer.getCenter()); +// mainContainer.setCenter(page); +// Controller controller = (Controller) loader.getController(); +// controller.setParent(this); +// } } diff --git a/src/main/java/seng302/Controllers/Controller.java b/src/main/java/seng302/Controllers/Controller.java index 792acd2c..c5d160e7 100644 --- a/src/main/java/seng302/Controllers/Controller.java +++ b/src/main/java/seng302/Controllers/Controller.java @@ -11,27 +11,17 @@ import java.util.ResourceBundle; * Created by fwy13 on 15/03/2017. */ public abstract class Controller implements Initializable { - protected App parent; + protected MainController parent; /** * Sets the parent of the application * * @param parent controller */ - public void setParent(App parent) { + public void setParent(MainController parent) { this.parent = parent; } - /** - * Sets the loads a pane into the parent. - * - * @param fxmlName fxml resource file to be loaded - * @throws Exception error in loading file - */ - public void loadPane(String fxmlName) throws Exception { - this.parent.loadPane(fxmlName); - } - /** * Initialisation class that is run on start up. * diff --git a/src/main/java/seng302/Controllers/MainController.java b/src/main/java/seng302/Controllers/MainController.java index fe6922a0..0d09391f 100644 --- a/src/main/java/seng302/Controllers/MainController.java +++ b/src/main/java/seng302/Controllers/MainController.java @@ -1,5 +1,10 @@ package seng302.Controllers; +import javafx.fxml.FXML; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.GridPane; +import seng302.RaceXMLReader; + import java.net.URL; import java.util.ResourceBundle; @@ -7,6 +12,16 @@ import java.util.ResourceBundle; * Created by fwy13 on 15/03/2017. */ public class MainController extends Controller { + @FXML StartController startController; + @FXML RaceController raceController; + + + public void beginRace(int scaleFactor) { + raceController.startRace(scaleFactor); + } + + + /** * Main Controller for the applications will house the menu and the displayed pane. @@ -16,6 +31,7 @@ public class MainController extends Controller { */ @Override public void initialize(URL location, ResourceBundle resources) { - + startController.setParent(this); + raceController.setParent(this); } } diff --git a/src/main/java/seng302/Controllers/RaceController.java b/src/main/java/seng302/Controllers/RaceController.java index 83b4ac5e..58c63304 100644 --- a/src/main/java/seng302/Controllers/RaceController.java +++ b/src/main/java/seng302/Controllers/RaceController.java @@ -1,16 +1,15 @@ package seng302.Controllers; -import com.github.bfsmith.geotimezone.TimeZoneLookup; -import com.github.bfsmith.geotimezone.TimeZoneResult; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.layout.GridPane; -import javafx.util.Callback; import org.xml.sax.SAXException; import seng302.Model.*; import seng302.RaceXMLReader; @@ -18,14 +17,8 @@ import seng302.RaceXMLReader; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.net.URL; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Calendar; import java.util.ResourceBundle; -import java.util.TimeZone; /** * Created by fwy13 on 15/03/2017. @@ -34,12 +27,12 @@ public class RaceController extends Controller { @FXML GridPane canvasBase; - ResizableRaceCanvas raceMap; + //user saved data for annotation display + private ArrayList presetAnno; + ResizableRaceCanvas raceMap; @FXML - GridPane startScreen; - @FXML - SplitPane ongoingRacePane; + SplitPane race; @FXML CheckBox showFPS; @FXML @@ -51,6 +44,17 @@ public class RaceController extends Controller { @FXML Label timeZone; + @FXML + CheckBox showName; + @FXML + CheckBox showAbbrev; + @FXML + CheckBox showSpeed; + @FXML + Button saveAnno; + @FXML + Button showSetAnno; + @FXML TableView boatInfoTable; @FXML @@ -89,26 +93,6 @@ public class RaceController extends Controller { boatPlacingColumn.setCellValueFactory(cellData -> cellData.getValue().positionProperty()); } - /** - * Begins the race with a scale factor of 15 - */ - public void startRace1Min() { - startRace(15); - } - - /** - * Begins the race with a scale factor of 3 - */ - public void startRace5Min() { - startRace(3); - } - - /** - * Begins the race with a scale factor of 1 - */ - public void startRaceNoScaling() { - startRace(1); - } @Override public void initialize(URL location, ResourceBundle resources) { @@ -131,7 +115,8 @@ public class RaceController extends Controller { * * @param scaleFactor scale value of race */ - private void startRace(int scaleFactor) { + public void startRace(int scaleFactor) { + RaceXMLReader raceXMLReader = null; try { raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml"); @@ -152,12 +137,12 @@ public class RaceController extends Controller { ArrayList legs = raceXMLReader.getLegs(); - ConstantVelocityRace race = new ConstantVelocityRace(boats, legs, this, scaleFactor); - race.initialiseBoats(); + ConstantVelocityRace newRace = new ConstantVelocityRace(boats, legs, this, scaleFactor); + newRace.initialiseBoats(); - BoatInRace[] startingBoats = new BoatInRace[race.getStartingBoats().size()]; + BoatInRace[] startingBoats = new BoatInRace[newRace.getStartingBoats().size()]; int i = 0; - for (BoatInRace boat : race.getStartingBoats()) { + for (BoatInRace boat : newRace.getStartingBoats()) { startingBoats[i] = boat; i++; } @@ -172,9 +157,9 @@ public class RaceController extends Controller { raceMap.setVisible(true); canvasBase.getChildren().add(raceMap); - startScreen.setVisible(false); - ongoingRacePane.setVisible(true); + race.setVisible(true); + //Initialize save annotation array, fps listener, and annotation listeners //timezone RaceClock raceClock = new RaceClock(raceXMLReader.getMark()); timeZone.textProperty().bind(raceClock.timeProperty()); @@ -182,7 +167,7 @@ public class RaceController extends Controller { initializeFPS(); initializeAnnotations(); - new Thread((race)).start(); + new Thread((newRace)).start(); } @@ -225,6 +210,7 @@ public class RaceController extends Controller { * Set up boat annotations */ private void initializeAnnotations() { + presetAnno = new ArrayList<>(); //listener for annotation showAnnotations.selectedProperty().addListener(new ChangeListener() { public void changed(ObservableValue ov, @@ -233,6 +219,51 @@ public class RaceController extends Controller { raceMap.update(); } }); + //listener for show name in annotation + showName.selectedProperty().addListener(new ChangeListener() { + public void changed(ObservableValue ov, + Boolean old_val, Boolean new_val) { + raceMap.toggleAnnoName(); + raceMap.update(); + } + }); + //listener for show abbreviation for annotation + showAbbrev.selectedProperty().addListener(new ChangeListener() { + public void changed(ObservableValue ov, + Boolean old_val, Boolean new_val) { + raceMap.toggleAnnoAbbrev(); + raceMap.update(); + } + }); + //listener to show speed for annotation + showSpeed.selectedProperty().addListener(new ChangeListener() { + public void changed(ObservableValue ov, + Boolean old_val, Boolean new_val) { + raceMap.toggleAnnoSpeed(); + raceMap.update(); + } + }); + //listener to save currently selected annotation + saveAnno.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent event) { + presetAnno.clear(); + presetAnno.add(showName.isSelected()); + presetAnno.add(showAbbrev.isSelected()); + presetAnno.add(showSpeed.isSelected()); + } + }); + //listener to show saved annotation + showSetAnno.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent event) { + if (presetAnno.size() > 0) { + showName.setSelected(presetAnno.get(0)); + showAbbrev.setSelected(presetAnno.get(1)); + showSpeed.setSelected(presetAnno.get(2)); + raceMap.update(); + } + } + }); } - } diff --git a/src/main/java/seng302/Controllers/StartController.java b/src/main/java/seng302/Controllers/StartController.java new file mode 100644 index 00000000..40a91de7 --- /dev/null +++ b/src/main/java/seng302/Controllers/StartController.java @@ -0,0 +1,86 @@ +package seng302.Controllers; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.GridPane; +import org.xml.sax.SAXException; +import seng302.Model.BoatInRace; +import seng302.RaceXMLReader; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.net.URL; +import java.util.ResourceBundle; + +/** + * Created by esa46 on 6/04/17. + */ +public class StartController extends Controller { + + @FXML private GridPane start; + + @FXML private TableView boatNameTable; + @FXML private TableColumn boatNameColumn; + @FXML private TableColumn boatCodeColumn; + + /** + * Begins the race with a scale factor of 15 + */ + public void startRace1Min() { + startRace(15); + } + + /** + * Begins the race with a scale factor of 3 + */ + public void startRace5Min() { + startRace(3); + } + + /** + * Begins the race with a scale factor of 1 + */ + public void startRaceNoScaling() { + startRace(1); + } + + private void startRace(Integer raceScale){ + start.setVisible(false); + + parent.beginRace(raceScale); + } + + @Override + public void initialize(URL location, ResourceBundle resources){ + initialiseTables(); + + + } + + private void initialiseTables() { + + RaceXMLReader raceXMLReader = null; + try { + raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml"); + } catch (IOException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } + + BoatInRace[] boats = new BoatInRace[raceXMLReader.getBoats().size()]; + boats = raceXMLReader.getBoats().toArray(boats); + ObservableList observableBoats = FXCollections.observableArrayList(boats); + boatNameTable.setItems(observableBoats); + boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().getName()); + boatCodeColumn.setCellValueFactory(new PropertyValueFactory<>("abbrev")); + + } + +} diff --git a/src/main/java/seng302/Model/BoatInRace.java b/src/main/java/seng302/Model/BoatInRace.java index 544eceb0..d306eb4c 100644 --- a/src/main/java/seng302/Model/BoatInRace.java +++ b/src/main/java/seng302/Model/BoatInRace.java @@ -217,7 +217,7 @@ public class BoatInRace extends Boat { } /** - * @return true if boat has finished, fals eif not + * @return true if boat has finished, false if not */ public boolean isFinished() { return this.finished; @@ -248,7 +248,7 @@ public class BoatInRace extends Boat { return position; } - public void setPosition(int position) { - this.position.set(Integer.toString(position + 1)); + public void setPosition(String position) { + this.position.set(position); } } diff --git a/src/main/java/seng302/Model/Race.java b/src/main/java/seng302/Model/Race.java index 14679424..825b7145 100644 --- a/src/main/java/seng302/Model/Race.java +++ b/src/main/java/seng302/Model/Race.java @@ -24,6 +24,7 @@ public abstract class Race implements Runnable { protected RaceController controller; protected int boatsFinished = 0; protected long totalTimeElapsed; + private int dnfChance = 1; //%percentage chance a boat fails at each checkpoint private int lastFPS = 20; @@ -31,7 +32,7 @@ public abstract class Race implements Runnable { protected int scaleFactor; private int SLEEP_TIME = 100; //time in milliseconds to pause in a paced loop - protected int PRERACE_TIME = 5000; //time in milliseconds to pause during pre-race + protected int PRERACE_TIME = 120000; //time in milliseconds to pause during pre-race private boolean timerEnabled = true; //boolean to determine if timer is ran /** @@ -54,6 +55,16 @@ public abstract class Race implements Runnable { } } + /** + * Sets the chance each boat has of failing at a gate or marker + * @param chance percentage chance a boat has of failing per checkpoint. + */ + protected void setDnfChance(int chance) { + if (chance >= 0 && chance <= 100) { + dnfChance = chance; + } + } + public void initialiseBoats() { @@ -85,7 +96,7 @@ public abstract class Race implements Runnable { setControllerListeners(); initialiseBoats(); if (timerEnabled) countdownTimer(); - simulateRace(); + //simulateRace(); } /** @@ -100,37 +111,35 @@ public abstract class Race implements Runnable { * Countdown timer until race starts. Use PRERACE_TIME to set countdown duration. */ protected void countdownTimer() { - long currentTime = System.currentTimeMillis(); - long startTime = currentTime + PRERACE_TIME; - long minutes; - long currentTimeInSeconds; - long remainingSeconds; - long hours; - long timeLeft; - long timeLoopEnded; + new AnimationTimer() { + long currentTime = System.currentTimeMillis(); + long startTime = currentTime + (PRERACE_TIME/scaleFactor); + long minutes; + long currentTimeInSeconds; + long remainingSeconds; + long hours; + long timeLeft; - while (currentTime <= startTime) { - timeLeft = startTime - currentTime; - if (timeLeft == 0 && controller != null) { - updateTime("Race is starting..."); - } else { - currentTimeInSeconds = timeLeft / 1000; - minutes = currentTimeInSeconds / 60; - remainingSeconds = currentTimeInSeconds % 60; - hours = minutes / 60; - minutes = minutes % 60; - if (controller != null) { - updateTime(String.format("Race clock: -%02d:%02d:%02d", hours, minutes, remainingSeconds)); - } - } - try { - timeLoopEnded = System.currentTimeMillis(); - Thread.sleep(SLEEP_TIME - (timeLoopEnded - currentTime)); - } catch (InterruptedException e) { - e.printStackTrace(); + @Override + public void handle(long arg0) { + timeLeft = startTime - currentTime; + if (timeLeft <= 0 && controller != null) { + updateTime("Race is starting..."); + stop(); + simulateRace(); + } else { + currentTimeInSeconds = (timeLeft*scaleFactor) / 1000; + minutes = currentTimeInSeconds / 60; + remainingSeconds = currentTimeInSeconds % 60; + hours = minutes / 60; + minutes = minutes % 60; + if (controller != null) { + updateTime(String.format("Race clock: -%02d:%02d:%02d", hours, minutes, remainingSeconds)); + } + } + currentTime = System.currentTimeMillis(); } - currentTime = System.currentTimeMillis(); - } + }.start(); } /** @@ -176,7 +185,7 @@ public abstract class Race implements Runnable { private boolean doNotFinish() { Random rand = new Random(); - return rand.nextInt(100) < 1; + return rand.nextInt(100) < dnfChance; } /** @@ -264,8 +273,21 @@ public abstract class Race implements Runnable { boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg()); } //Update the boat display table in the GUI to reflect the leg change - FXCollections.sort(startingBoats, (a, b) -> b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber()); - boat.setPosition(startingBoats.indexOf(boat)); + updatePositions(); + } + } + + /** + * Update position of boats in race, no position if on starting leg or DNF. + */ + private void updatePositions() { + FXCollections.sort(startingBoats, (a, b) -> b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber()); + for(BoatInRace boat: startingBoats) { + if(boat != null) { + boat.setPosition(Integer.toString(startingBoats.indexOf(boat) + 1)); + if (boat.getCurrentLeg().getName().equals("DNF") || boat.getCurrentLeg().getLegNumber() == 0) + boat.setPosition("-"); + } } } diff --git a/src/main/java/seng302/Model/ResizableRaceCanvas.java b/src/main/java/seng302/Model/ResizableRaceCanvas.java index 10c0638d..befa173c 100644 --- a/src/main/java/seng302/Model/ResizableRaceCanvas.java +++ b/src/main/java/seng302/Model/ResizableRaceCanvas.java @@ -13,6 +13,7 @@ import seng302.GraphCoordinate; import seng302.RaceMap; import java.util.ArrayList; +import java.util.Arrays; /** * This creates a JavaFX Canvas that is fills it's parent. @@ -25,6 +26,9 @@ public class ResizableRaceCanvas extends Canvas { private BoatInRace[] boats; private RaceController controller; private boolean raceAnno = true; + private boolean annoName = true; + private boolean annoAbbrev = true; + private boolean annoSpeed = true; private ArrayList raceBoundaries; double[] xpoints = {}, ypoints = {}; @@ -163,11 +167,25 @@ public class ResizableRaceCanvas extends Canvas { * Display given name and speed of boat at a graph coordinate * * @param name name of the boat + * @param abbrev abbreviation of the boat name * @param speed speed of the boat * @param coordinate coordinate the text appears */ - private void displayText(String name, double speed, GraphCoordinate coordinate) { - String text = String.format("%s, %2$.2fkn", name, speed); + private void displayText(String name, String abbrev, double speed, GraphCoordinate coordinate) { + String text = ""; + //Check name toggle value + if (annoName){ + text += String.format("%s ", name); + } + //Check abbreviation toggle value + if (annoAbbrev){ + text += String.format("%s ", abbrev); + } + //Check speed toggle value + if (annoSpeed){ + text += String.format("%.2fkn", speed); + } + //String text = String.format("%s, %2$.2fkn", name, speed); long xCoord = coordinate.getX() + 20; long yCoord = coordinate.getY(); if (xCoord + (text.length() * 7) >= getWidth()) { @@ -187,6 +205,9 @@ public class ResizableRaceCanvas extends Canvas { this.updateBoats(); } + /** + * Draw boundary of the race. + */ public void drawBoundaries() { if (this.raceBoundaries == null) { return; @@ -263,6 +284,39 @@ public class ResizableRaceCanvas extends Canvas { } } + /** + * Toggle name display in annotation + */ + public void toggleAnnoName() { + if (annoName) { + annoName = false; + } else { + annoName = true; + } + } + + /** + * Toggle abbreviation display in annotation + */ + public void toggleAnnoAbbrev() { + if (annoAbbrev) { + annoAbbrev = false; + } else { + annoAbbrev = true; + } + } + + /** + * Toggle speed display in annotation + */ + public void toggleAnnoSpeed() { + if (annoSpeed) { + annoSpeed = false; + } else { + annoSpeed = true; + } + } + /** * Draws boats while race in progress, when leg heading is set. */ @@ -283,7 +337,7 @@ public class ResizableRaceCanvas extends Canvas { } if (raceAnno) - displayText(boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition())); + displayText(boat.toString(), boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition())); } } } diff --git a/src/main/resources/scenes/main.fxml b/src/main/resources/scenes/main.fxml new file mode 100644 index 00000000..d94c8b59 --- /dev/null +++ b/src/main/resources/scenes/main.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/scenes/mainpane.fxml b/src/main/resources/scenes/mainpane.fxml deleted file mode 100644 index ca502553..00000000 --- a/src/main/resources/scenes/mainpane.fxml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/src/main/resources/scenes/race.fxml b/src/main/resources/scenes/race.fxml new file mode 100644 index 00000000..1a795e84 --- /dev/null +++ b/src/main/resources/scenes/race.fxml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +