package seng302.Mock; import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import seng302.Controllers.FinishController; import seng302.Controllers.RaceController; import seng302.GPSCoordinate; import seng302.Model.Boat; import seng302.Model.Leg; import seng302.Model.Marker; import seng302.Networking.Messages.BoatLocation; import seng302.Networking.Messages.BoatStatus; import seng302.Networking.Messages.Enums.BoatStatusEnum; import seng302.VisualiserInput; import java.util.List; /** * The Class used to view the race streamed. */ public class StreamedRace implements Runnable { private final VisualiserInput visualiserInput; private final ObservableList startingBoats; private final ObservableList boatMarkers; private final List legs; private RaceController controller; protected FinishController finishController; private int boatsFinished = 0; private long totalTimeElapsed; private int lastFPS = 20; public StreamedRace(VisualiserInput visualiserInput, RaceController controller) { StreamedCourse course = visualiserInput.getCourse(); this.startingBoats = FXCollections.observableArrayList(course.getBoats()); this.boatMarkers = FXCollections.observableArrayList(course.getMarkers()); this.legs = course.getLegs(); this.legs.add(new Leg("Finish", this.legs.size())); this.controller = controller; if (startingBoats != null && startingBoats.size() > 0) { initialiseBoats(); } this.visualiserInput = visualiserInput; } private void initialiseBoats() { Leg officialStart = legs.get(0); String name = officialStart.getName(); Marker endCompoundMark = officialStart.getEndMarker(); for (Boat boat : startingBoats) { if (boat != null) { Leg startLeg = new Leg(name, 0); startLeg.setEndMarker(endCompoundMark); boat.setCurrentLeg(startLeg, controller.getRaceClock()); boat.setTimeSinceLastMark(controller.getRaceClock().getTime()); } } } /** * Checks if the boat cannot finish the race * @return True if boat cannot finish the race */ protected boolean doNotFinish() { // DNF is no longer random and is now determined by a dnf packet return false; } /** * Checks the position of the boat. * * @param boat Boat that the position is to be updated for. * @param timeElapsed Time that has elapse since the start of the the race. */ private void checkPosition(Boat boat, long timeElapsed) { boolean legChanged = false; StreamedCourse raceData = visualiserInput.getCourse(); BoatStatus boatStatusMessage = visualiserInput.getBoatStatusMap().get(boat.getSourceID()); if (boatStatusMessage != null) { BoatStatusEnum boatStatusEnum = BoatStatusEnum.fromByte(boatStatusMessage.getBoatStatus()); int legNumber = boatStatusMessage.getLegNumber(); if (legNumber >= 1 && legNumber < legs.size()) { if (boat.getCurrentLeg() != legs.get(legNumber)){ boat.setCurrentLeg(legs.get(legNumber), controller.getRaceClock()); legChanged = true; } } if (boatStatusEnum == BoatStatusEnum.RACING) { boat.addTrackPoint(boat.getCurrentPosition()); } else if (boatStatusEnum == BoatStatusEnum.DNF) { boat.setDnf(true); } else if (boatStatusEnum == BoatStatusEnum.FINISHED || legNumber == raceData.getLegs().size()) { boatsFinished++; boat.setTimeFinished(timeElapsed); boat.setFinished(true); } } if (legChanged) { //Update the boat display table in the GUI to reflect the leg change updatePositions(); controller.updateSparkline(startingBoats); } } /** * Updates the boat's gps coordinates * * @param boat to be updated */ private void updatePosition(Boat boat) { int sourceID = boat.getSourceID(); BoatLocation boatLocation = visualiserInput.getBoatLocationMessage(sourceID); BoatStatus boatStatus = visualiserInput.getBoatStatusMessage(sourceID); if(boatLocation != null) { double lat = boatLocation.getLatitudeDouble(); double lon = boatLocation.getLongitudeDouble(); boat.setCurrentPosition(new GPSCoordinate(lat, lon)); boat.setHeading(boatLocation.getHeadingDegrees()); boat.setEstTime(convertEstTime(boatStatus.getEstTimeAtNextMark(), boatLocation.getTime())); double MMPS_TO_KN = 0.001944; boat.setVelocity(boatLocation.getBoatSOG() * MMPS_TO_KN); } } /** * Updates the boat's gps coordinates * * @param mark to be updated */ private void updateMarker(Marker mark) { int sourceID = mark.getSourceId1(); BoatLocation boatLocation1 = visualiserInput.getBoatLocationMessage(sourceID); if(boatLocation1 != null) { double lat = boatLocation1.getLatitudeDouble(); double lon = boatLocation1.getLongitudeDouble(); mark.setCurrentPosition1(new GPSCoordinate(lat, lon)); } int sourceID2 = mark.getSourceId2(); BoatLocation boatLocation2 = visualiserInput.getBoatLocationMessage(sourceID2); if(boatLocation2 != null) { double lat = boatLocation2.getLatitudeDouble(); double lon = boatLocation2.getLongitudeDouble(); mark.setCurrentPosition2(new GPSCoordinate(lat, lon)); } } public void setController(RaceController controller) { this.controller = controller; } /** * Runnable for the thread. */ public void run() { setControllerListeners(); Platform.runLater(() -> controller.createSparkLine(startingBoats)); initialiseBoats(); startRaceStream(); } /** * Update the calculated fps to the fps label * * @param fps The new calculated fps value */ private void updateFPS(int fps) { Platform.runLater(() -> controller.setFrames("FPS: " + fps)); } /** * Starts the Race Simulation, playing the race start to finish with the timescale. * This prints the boats participating, the order that the events occur in time order, and the respective information of the events. */ private void startRaceStream() { System.setProperty("javafx.animation.fullspeed", "true"); for (Boat boat : startingBoats) { boat.setStarted(true); } new AnimationTimer() { final long timeRaceStarted = System.currentTimeMillis(); //start time of loop int fps = 0; //init fps value long timeCurrent = System.currentTimeMillis(); //current time @Override public void handle(long arg0) { totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted; for (Boat boat : startingBoats) { if (boat != null && !boat.isFinished()) { updatePosition(boat); checkPosition(boat, totalTimeElapsed); } } for (Marker mark: boatMarkers){ if (mark != null){ updateMarker(mark); } } if (visualiserInput.getRaceStatus().isFinished()) { controller.finishRace(startingBoats); stop(); } controller.updateMap(startingBoats, boatMarkers); fps++; if ((System.currentTimeMillis() - timeCurrent) > 1000) { updateFPS(fps); lastFPS = fps; fps = 0; timeCurrent = System.currentTimeMillis(); } } }.start(); } /** * 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(Boat boat: startingBoats) { if(boat != null) { boat.setPosition(Integer.toString(startingBoats.indexOf(boat) + 1)); if (boat.isDnf() || !boat.isStarted() || boat.getCurrentLeg().getLegNumber() < 0) boat.setPosition("-"); } } } /** * Update call for the controller. */ private void setControllerListeners() { if (controller != null) controller.setInfoTable(this); } /** * Returns the boats that have started the race. * * @return ObservableList of Boat class that participated in the race. * @see ObservableList * @see Boat */ public ObservableList getStartingBoats() { return startingBoats; } /** * Takes an estimated time an event will occur, and converts it to the number of seconds before the event will occur. * * @param estTimeMillis * @return int difference between time the race started and the estimated time */ private int convertEstTime(long estTimeMillis, long currentTime) { long estElapsedMillis = estTimeMillis - currentTime; int estElapsedSecs = Math.round(estElapsedMillis/1000); return estElapsedSecs; } }