diff --git a/src/main/java/seng302/Controllers/RaceController.java b/src/main/java/seng302/Controllers/RaceController.java index 91a692e1..fcaf8dec 100644 --- a/src/main/java/seng302/Controllers/RaceController.java +++ b/src/main/java/seng302/Controllers/RaceController.java @@ -4,12 +4,18 @@ package seng302.Controllers; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; +import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.SplitPane; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; +import javafx.scene.control.TitledPane; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; import javafx.scene.layout.GridPane; import javafx.util.Callback; import seng302.Constants; @@ -22,12 +28,12 @@ import java.util.ResourceBundle; /** * Created by fwy13 on 15/03/2017. */ -public class RaceController extends Controller { - - ResizableRaceCanvas raceMap; +public class RaceController extends Controller{ @FXML AnchorPane canvasBase; + ResizableRaceCanvas raceMap; + @FXML GridPane startScreen; @FXML @@ -36,6 +42,11 @@ public class RaceController extends Controller { @FXML Label timer; + + @FXML + Label FPS; + + @FXML TableView boatInfoTable; @FXML @@ -128,12 +139,12 @@ public class RaceController extends Controller { ConstantVelocityRace race = new ConstantVelocityRace(boats, legs, this, scaleFactor); - (new Thread(race)).start(); + new Thread((race)).start(); } + /** - * Generates an example race course (Bermuda 2017) - * + * Function for the Bermuda Race. * @return legs in the Bermuda Race. */ private ArrayList generateBermudaCourseLegs() { @@ -172,4 +183,7 @@ public class RaceController extends Controller { timer.setText(time); } + public void setFrames(String fps) { FPS.setText((fps)); } + + } diff --git a/src/main/java/seng302/Model/Boat.java b/src/main/java/seng302/Model/Boat.java index 3114ad07..2bf835b7 100644 --- a/src/main/java/seng302/Model/Boat.java +++ b/src/main/java/seng302/Model/Boat.java @@ -21,7 +21,7 @@ public class Boat { */ public Boat(String name, double velocity, String abbrev) { this.velocity = velocity; - this.velocityProp = new SimpleStringProperty(String.valueOf(velocity)); + this.velocityProp = new SimpleStringProperty(String.valueOf(velocity* 1.94384)); this.abbrev = abbrev; this.name = new SimpleStringProperty(name); } diff --git a/src/main/java/seng302/Model/Race.java b/src/main/java/seng302/Model/Race.java index 8bc8c209..839b0421 100644 --- a/src/main/java/seng302/Model/Race.java +++ b/src/main/java/seng302/Model/Race.java @@ -1,10 +1,14 @@ package seng302.Model; +import javafx.animation.AnimationTimer; import javafx.application.Platform; +import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; +import javafx.collections.ObservableArray; import javafx.collections.ObservableList; import seng302.Controllers.RaceController; +import seng302.GPSCoordinate; import java.util.ArrayList; @@ -23,14 +27,13 @@ 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 = 100;//Integer.MAX_VALUE; //time in milliseconds to pause during pre-race + protected int PRERACE_TIME = 10000;//Integer.MAX_VALUE; //time in milliseconds to pause during pre-race private boolean timerEnabled = true; /** * Initailiser for Race - * * @param boats Takes in an array of boats that are participating in the race. - * @param legs Number of marks in order that the boats pass in order to complete the race. + * @param legs Number of marks in order that the boats pass in order to complete the race. */ public Race(BoatInRace[] boats, ArrayList legs, RaceController controller, int scaleFactor) { if (boats.length > 0) { @@ -50,7 +53,6 @@ public abstract class Race implements Runnable { /** * Constructor for Race class - * * @param boats boats participating in the race. * @param legs legs that there are in the race. */ @@ -68,37 +70,41 @@ public abstract class Race implements Runnable { this.scaleFactor = scaleFactor; } - /** * Runnable for the thread. */ public void run() { setControllerListeners(); preRace(); - if (timerEnabled) countdownTimer(); + if(timerEnabled) countdownTimer(); simulateRace(); } + /** + * Disable the timer + */ public void disableTimer() { timerEnabled = false; } /** - * Initialises the boats, - * Sets the boats' current to the first leg in the race + * Set up the state in waiting for the race starts. */ private void preRace() { //show the boats participating. + System.out.println("Boats Participating:"); + System.out.println("===================="); for (int i = 0; i < startingBoats.size(); i++) { if (startingBoats.get(i) != null) { + System.out.println(i + 1 + ". " + startingBoats.get(i).toString() + ", Speed: " + + Math.round(startingBoats.get(i).getVelocity() * 1.94384) + "kn"); startingBoats.get(i).setCurrentLeg(legs.get(0)); } } } - /** - * Prerace timer showing time until the race will begin, as a negative value + * Countdown timer until race starts. Use PRERACE_TIME to set countdown duration. */ protected void countdownTimer() { long currentTime = System.currentTimeMillis(); @@ -112,17 +118,13 @@ public abstract class Race implements Runnable { 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)); - } + currentTimeInSeconds = timeLeft / 1000; + minutes = currentTimeInSeconds / 60; + remainingSeconds = currentTimeInSeconds % 60; + hours = minutes / 60; + minutes = minutes % 60; + if (controller != null) { + updateTime(String.format("Time until race starts: %02d:%02d:%02d", hours, minutes, remainingSeconds)); } try { timeLoopEnded = System.currentTimeMillis(); @@ -135,8 +137,8 @@ public abstract class Race implements Runnable { } /** - * Takes elapsed time in minutes and scales it, converts to hh:mm:ss format - * @return String formatted race time, scaled + * Takes total time elapsed and format to hour:minute:second + * @return Formatted time as string */ protected String calcTimer() { long minutes; @@ -145,23 +147,23 @@ public abstract class Race implements Runnable { long hours; currentTimeInSeconds = totalTimeElapsed / 1000; - long scaledTimeInSeconds = currentTimeInSeconds * scaleFactor; - minutes = scaledTimeInSeconds / 60; - remainingSeconds = scaledTimeInSeconds % 60; + minutes = currentTimeInSeconds / 60; + remainingSeconds = currentTimeInSeconds % 60; hours = minutes / 60; minutes = minutes % 60; return String.format("Race clock: %02d:%02d:%02d", hours, minutes, remainingSeconds); } /** - * Updates the GUI race clock - * @param time + * Updates the calculated time to the timer label + * @param time The calculated time from calcTimer() method */ - protected void updateTime(String time) { + protected void updateTime(String time){ + Platform.runLater(() -> {controller.setTimer(time);}); + } - Platform.runLater(() -> { - controller.setTimer(time); - }); + private void updateFPS(int fps) { + Platform.runLater(() -> {controller.setFrames("FPS: " + fps);}); } /** @@ -170,42 +172,66 @@ public abstract class Race implements Runnable { */ private void simulateRace() { - long timeRaceStarted = System.currentTimeMillis(); - long timeLoopStarted; - long timeLoopEnded; + System.setProperty("javafx.animation.fullspeed", "true"); - while (boatsFinished < startingBoats.size()) { - timeLoopStarted = System.currentTimeMillis(); - totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted; + new AnimationTimer() { + long timeRaceStarted = System.currentTimeMillis(); + int fps = 0; + long timeCurrent = System.currentTimeMillis(); - for (BoatInRace boat : startingBoats) { - if (boat != null && !boat.isFinished()) { - updatePosition(boat, SLEEP_TIME); - checkPosition(boat, totalTimeElapsed); - } - } + @Override + public void handle(long arg0) { - if (controller != null) controller.updateMap(startingBoats); - if (timerEnabled) updateTime(calcTimer()); - try { - timeLoopEnded = System.currentTimeMillis(); - Thread.sleep(SLEEP_TIME - (timeLoopEnded - timeLoopStarted)); - } catch (InterruptedException e) { - return; - } - } + /*long timeLoopStarted; + long timeLoopEnded; + int fps = 0;*/ + + if (boatsFinished < startingBoats.size()) { + //timeLoopStarted = System.currentTimeMillis(); + totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted; + + for (BoatInRace boat : startingBoats) { + if (boat != null && !boat.isFinished()) { + updatePosition(boat, SLEEP_TIME); + checkPosition(boat, totalTimeElapsed); + } + } + + if (controller != null) controller.updateMap(startingBoats); + if (timerEnabled) + updateTime(calcTimer()); + } + fps++; + if ((System.currentTimeMillis()-timeCurrent) > 1000){ + updateFPS(fps); + fps = 0; + timeCurrent = System.currentTimeMillis(); + } + ; + + + /*fps++; + try { + timeLoopEnded = System.currentTimeMillis(); + Thread.sleep(SLEEP_TIME - (timeLoopEnded - timeLoopStarted)); + } catch (InterruptedException e) { + return; + }*/ + }; + //System.out.println("Avg fps:" + fps/(totalTimeElapsed/1000)); + }.start(); } /** * Checks the position of the boat, this updates the boats current position. - * - * @param boat Boat that the postion is to be updated for. + * @param boat Boat that the postion is to be updated for. * @param timeElapsed Time that has elapse since the start of the the race. * @see BoatInRace */ protected void checkPosition(BoatInRace boat, long timeElapsed) { - if (boat.getDistanceTravelledInLeg() > boat.getCurrentLeg().getDistance()) { + if (boat.getDistanceTravelledInLeg() > boat.getCurrentLeg().getDistance()){ +// updateController(); //boat has passed onto new leg if (boat.getCurrentLeg().getName().equals("Finish")) { //boat has finished @@ -213,16 +239,13 @@ public abstract class Race implements Runnable { boat.setFinished(true); boat.setTimeFinished(timeElapsed); } else { - //Calculate how much the boat overshot the marker by boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg() - boat.getCurrentLeg().getDistance()); - //Move boat on to next leg Leg nextLeg = legs.get(boat.getCurrentLeg().getLegNumber() + 1); + boat.setCurrentLeg(nextLeg); - //Add overshoot distance into the distance travelled for the next leg 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()); + FXCollections.sort(startingBoats, (a,b) -> b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber()); } } @@ -230,12 +253,11 @@ public abstract class Race implements Runnable { * Update call for the controller. */ protected void setControllerListeners() { - if (controller != null) controller.setInfoTable(this); + if(controller != null) controller.setInfoTable(this); } /** * Returns the boats that have started the race. - * * @return ObservableList of BoatInRace class that participated in the race. * @see ObservableList * @see BoatInRace @@ -244,12 +266,12 @@ public abstract class Race implements Runnable { return startingBoats; } - /** - * Updates the boat's gps coordinates depending on time elapsed - * @param boat - * @param millisecondsElapsed + * This function is a function that generates the Race and populates the events list. + * Is automatically called by the initialiser function, so that simulateRace() does not return an empty race. + * @see Race#simulateRace() */ + protected abstract void updatePosition(BoatInRace boat, int millisecondsElapsed); } diff --git a/src/main/java/seng302/Model/ResizableRaceCanvas.java b/src/main/java/seng302/Model/ResizableRaceCanvas.java index 46c19ca5..fb9b65c9 100644 --- a/src/main/java/seng302/Model/ResizableRaceCanvas.java +++ b/src/main/java/seng302/Model/ResizableRaceCanvas.java @@ -11,6 +11,11 @@ import seng302.GPSCoordinate; import seng302.GraphCoordinate; import seng302.RaceMap; +import java.awt.*; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Random; + /** * This creates a JavaFX Canvas that is fills it's parent. * Cannot be downsized. @@ -56,9 +61,8 @@ public class ResizableRaceCanvas extends Canvas { /** * Displays the mark of a race as a circle. - * * @param graphCoordinate Latitude and Logintude in GraphCoordinate that it is to be displayed as. - * @param paint Colour the mark is to be coloured. + * @param paint Colour the mark is to be coloured. * @see GraphCoordinate * @see Color * @see Paint @@ -70,10 +74,9 @@ public class ResizableRaceCanvas extends Canvas { /** * Displays a line on the map with rectangles on the starting and ending point of the line. - * * @param graphCoordinateA Starting Point of the line in GraphCoordinate. * @param graphCoordinateB End Point of the line in GraphCoordinate. - * @param paint Colour the line is to coloured. + * @param paint Colour the line is to coloured. * @see GraphCoordinate * @see Color * @see Paint @@ -88,9 +91,8 @@ public class ResizableRaceCanvas extends Canvas { /** * Display a point on the Canvas - * * @param graphCoordinate Coordinate that the point is to be displayed at. - * @param paint Colour that the boat is to be coloured. + * @param paint Colour that the boat is to be coloured. * @see GraphCoordinate * @see Paint * @see Color @@ -102,26 +104,25 @@ public class ResizableRaceCanvas extends Canvas { /** * Displays an arrow on the Canvas - * * @param coordinate Coordinate that the arrow is to be displayed at. - * @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up). + * @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up). * @see GraphCoordinate */ public void displayArrow(GraphCoordinate coordinate, int angle) { gc.save(); - rotate(angle, coordinate.getX(), coordinate.getY()); - gc.fillPolygon(new double[]{coordinate.getX() - 12, coordinate.getX() - 6, coordinate.getX(), coordinate.getX() - 4, coordinate.getX() - 4, coordinate.getX() - 8, coordinate.getX() - 8}, - new double[]{coordinate.getY() - 5, coordinate.getY() - 20, coordinate.getY() - 5, coordinate.getY() - 5, coordinate.getY() + 20, coordinate.getY() + 20, coordinate.getY() - 5}, + rotate(angle, coordinate.getX(),coordinate.getY()); + gc.setFill(Color.BLACK); + gc.fillPolygon(new double[]{coordinate.getX()-12, coordinate.getX()-6, coordinate.getX(), coordinate.getX()-4, coordinate.getX()-4, coordinate.getX()-8, coordinate.getX()-8}, + new double[]{coordinate.getY()-5, coordinate.getY()-20, coordinate.getY()-5, coordinate.getY()-5, coordinate.getY()+20, coordinate.getY()+20, coordinate.getY()-5}, 7); gc.restore(); } /** * Rotates things on the canvas Note: this must be called in between gc.save() and gc.restore() else they will rotate everything - * * @param angle Bearing angle to rotate at in degrees - * @param px Pivot point x of rotation. - * @param py Pivot point y of rotation. + * @param px Pivot point x of rotation. + * @param py Pivot point y of rotation. */ private void rotate(double angle, double px, double py) { Rotate r = new Rotate(angle, px, py); @@ -130,14 +131,22 @@ public class ResizableRaceCanvas extends Canvas { /** * Display given name and speed of boat at a graph coordinate - * - * @param name name of the boat - * @param speed speed of the boat + * @param name name of the boat + * @param speed speed of the boat * @param coordinate coordinate the text appears */ - public void displayText(String name, double speed, GraphCoordinate coordinate) { - String text = name + ", " + speed + " knots"; - gc.fillText(text, coordinate.getX() + 20, coordinate.getY()); + public void displayText(String name, double speed, GraphCoordinate coordinate){ + String text = String.format("%s, %2$.2f knots", name, speed); + //System.out.println(text.length()*7); + long xCoord = coordinate.getX()+20; + long yCoord = coordinate.getY(); + if (xCoord+(text.length()*7) >= getWidth()){ + xCoord -= text.length()*7; + } + if (yCoord-(text.length()*2) <= 0){ + yCoord += 30; + } + gc.fillText(text, xCoord, yCoord); } /** @@ -193,8 +202,7 @@ public class ResizableRaceCanvas extends Canvas { /** * Draws a boat at a certain GPSCoordinate - * - * @param colour Colour to colour boat. + * @param colour Colour to colour boat. * @param gpsCoordinates GPScoordinate that the boat is to be drawn at. * @see GPSCoordinate * @see Color