diff --git a/.mailmap b/.mailmap index b774add7..b68bd189 100644 --- a/.mailmap +++ b/.mailmap @@ -16,8 +16,9 @@ # https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html # http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/ Erika Savell -Connor Taylor-Brown +Connor Taylor-Brown Fraser Cope -Jessica Syder Jessica Syder +Jessica Syder Joseph Gardner -Hamish Ball hba56 \ No newline at end of file +Hamish Ball +David Wu \ No newline at end of file diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index 1e1b8966..602aed43 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -63,6 +63,8 @@ public class Event { */ private SourceIdAllocator sourceIdAllocator; + private RaceLogic raceLogic; + private Thread raceThread; private Thread connectionThread; @@ -114,14 +116,17 @@ public class Event { //Read XML files. try { - //this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90); - this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8)); + if(mapIndex==4){ - //this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000); - this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, -1, true); + //This is the tutorial map. + this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000); + this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, -1); + } else { - this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, windAngle, false); + this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8)); + this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, windAngle); this.raceXML = RaceXMLCreator.scaleRaceSize(raceXML, windSpeed, 15 * 60 * 1000); + } this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8); @@ -171,20 +176,20 @@ public class Event { } - RaceLogic newRace = new RaceLogic( + this.raceLogic = new RaceLogic( mockRace, this.latestMessages, this.compositeCommand); - this.raceThread = new Thread(newRace, "Event.Start()->RaceLogic thread"); + this.raceThread = new Thread(raceLogic, "Event.Start()->RaceLogic thread"); raceThread.start(); //Create connection acceptor. - this.sourceIdAllocator = new SourceIdAllocator(newRace.getRace()); + this.sourceIdAllocator = new SourceIdAllocator(raceLogic.getRace()); try { - this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace); + this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, raceLogic); } catch (IOException e) { throw new EventConstructionException("Could not create ConnectionAcceptor.", e); @@ -217,16 +222,13 @@ public class Event { } public static String setRaceXMLAtCurrentTimeToNow(String raceXML, long racePreStartTime, long racePreparatoryTime){ - //The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute. - long millisecondsToAdd = racePreStartTime + racePreparatoryTime; - - long secondsToAdd = millisecondsToAdd / 1000; + long minutesToAdd = 10; DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); ZonedDateTime creationTime = ZonedDateTime.now(); raceXML = raceXML.replace("RACE_CREATION_TIME", dateFormat.format(creationTime)); - raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd))); + raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusMinutes(minutesToAdd))); return raceXML; } @@ -241,4 +243,9 @@ public class Event { return new HostGame(ip, 3779,(byte) 1,(byte) 1, RaceStatusEnum.PRESTART, (byte) 1, (byte) 1); } + + public RaceLogic getRaceLogic() { + return raceLogic; + } + } diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 9ede32d1..43945a46 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -61,12 +61,17 @@ public class MockRace extends RaceState { */ private Polars polars; - private ActiveObserverCommand activeObserverCommand; + private Map activeObserverCommands; private long racePreStartTime = Constants.RacePreStartTime; private long racePreparatoryTime = Constants.RacePreparatoryTime; + /** + * True if the race has been manually started, false otherwise. + */ + private boolean hasBeenStarted = false; + /** * Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and sends events to the given mockOutput. * @param boatDataSource Data source for boat related data (yachts and marker boats). @@ -82,7 +87,7 @@ public class MockRace extends RaceState { this.setRaceDataSource(raceDataSource); this.setRegattaDataSource(regattaDataSource); - this.activeObserverCommand = new ActiveObserverCommand(); + this.activeObserverCommands = new HashMap<>(); this.polars = polars; this.scaleFactor = timeScale; @@ -127,6 +132,7 @@ public class MockRace extends RaceState { getRaceDataSource().getParticipants().add(sourceID); this.boats.add(mockBoat); + this.activeObserverCommands.put(boat.getSourceID(), new ActiveObserverCommand()); getRaceDataSource().incrementSequenceNumber(); @@ -157,6 +163,23 @@ public class MockRace extends RaceState { } + /** + * Delays the start of the race, if needed, to ensure that the race doesn't start until host wants it to. + * If the time until start is less that 5 minutes, it is extended to 10 minutes. + */ + public void delayRaceStart() { + long timeToStart = getRaceDataSource().getStartDateTime().toInstant().toEpochMilli() - System.currentTimeMillis(); + + long fiveMinutesMilli = 5 * 60 * 1000; + long tenMinutesMilli = 10 * 60 * 1000; + + if ((timeToStart < fiveMinutesMilli) && (timeToStart > 0) && !hasBeenStarted) { + startRace(tenMinutesMilli, false); + } + + } + + /** * Updates the race status enumeration based on the current time. */ @@ -165,7 +188,6 @@ public class MockRace extends RaceState { //The millisecond duration of the race. Negative means it hasn't started, so we flip sign. long timeToStart = - this.getRaceClock().getDurationMilli(); - if (timeToStart > racePreStartTime) { //Time > 3 minutes is the prestart period. this.setRaceStatusEnum(RaceStatusEnum.PRESTART); @@ -195,6 +217,26 @@ public class MockRace extends RaceState { this.racePreparatoryTime = racePreparatoryTime; } + public long getRacePreparatoryTime() { + return racePreparatoryTime; + } + + + /** + * Starts the race in #timeToStartMilli milliseconds. + * @param timeToStartMilli Millseconds before starting the race. + * @param manualStart True if the race has been manually started, false otherwise. + */ + public void startRace(long timeToStartMilli, boolean manualStart) { + this.hasBeenStarted = manualStart; + ZonedDateTime startTime = ZonedDateTime.now().plus(timeToStartMilli, ChronoUnit.MILLIS); + + getRaceDataSource().setStartDateTime(startTime); + getRaceDataSource().incrementSequenceNumber(); + + this.getRaceClock().setStartingTime(getRaceDataSource().getStartDateTime()); + } + /** * Sets the status of all boats in the race to RACING. */ @@ -711,11 +753,11 @@ public class MockRace extends RaceState { super.setChanged(); } - public void addVelocityCommand(ObserverCommand c) { - this.activeObserverCommand.changeVelocityCommand(this, c); + public void addVelocityCommand(ObserverCommand c, int boatId) { + this.activeObserverCommands.get(boatId).changeVelocityCommand(this, c); } - public void addAngularCommand(ObserverCommand c) { - this.activeObserverCommand.changeAngularCommand(this, c); + public void addAngularCommand(ObserverCommand c, int boatId) { + this.activeObserverCommands.get(boatId).changeAngularCommand(this, c); } } diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index 0110716e..8814733a 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -81,6 +81,8 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer { //Update race time. race.updateRaceTime(currentTime); + race.delayRaceStart(); + //Update the race status based on the current time. race.updateRaceStatusEnum(); diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CollisionCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/CollisionCommand.java index 52ec2b34..901ac860 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CollisionCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CollisionCommand.java @@ -36,6 +36,7 @@ public class CollisionCommand extends ObserverCommand { @Override public void update(Observable o, Object arg) { if(GPSCoordinate.calculateDistanceMeters(boat.getPosition(), startingPosition) < distance) { + boat.setVelocityDefault(false); boat.setPosition(GPSCoordinate.calculateNewPosition(boat.getPosition(), 3, azimuth)); } else { race.deleteObserver(this); diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/SailsCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/SailsCommand.java index ac4877c4..826e697b 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/SailsCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/SailsCommand.java @@ -13,7 +13,7 @@ public class SailsCommand extends ObserverCommand { public SailsCommand(MockRace race, MockBoat boat, boolean sailsOut) { super(race, boat); - race.addVelocityCommand(this); + race.addVelocityCommand(this, boat.getSourceID()); this.sailsOut = sailsOut; } @@ -37,15 +37,18 @@ public class SailsCommand extends ObserverCommand { public void update(Observable o, Object arg) { double acceleration = 0.5; - if(sailsOut && boat.getCurrentSpeed() < goalVelocity) { - boat.setCurrentSpeed(Math.min(goalVelocity, boat.getCurrentSpeed() + acceleration)); - } else if (!sailsOut && boat.getCurrentSpeed() > goalVelocity) { - // Apply deceleration to strictly 0 speed - boat.setCurrentSpeed(Math.max(0, boat.getCurrentSpeed() - acceleration)); - } else { - // Release boat from SailsCommand control - if(sailsOut) boat.setVelocityDefault(true); - race.deleteObserver(this); + if (!boat.isColliding()) { + boat.setVelocityDefault(false); + if (sailsOut && boat.getCurrentSpeed() < goalVelocity) { + boat.setCurrentSpeed(Math.min(goalVelocity, boat.getCurrentSpeed() + acceleration)); + } else if (!sailsOut && boat.getCurrentSpeed() > goalVelocity) { + // Apply deceleration to strictly 0 speed + boat.setCurrentSpeed(Math.max(0, boat.getCurrentSpeed() - acceleration)); + } else { + // Release boat from SailsCommand control + if (sailsOut) boat.setVelocityDefault(true); + race.deleteObserver(this); + } } } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index e10ee74a..78142da2 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -22,7 +22,7 @@ public class TackGybeCommand extends ObserverCommand { */ public TackGybeCommand(MockRace race, MockBoat boat) { super(race, boat); - race.addAngularCommand(this); + race.addAngularCommand(this, boat.getSourceID()); } @Override diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java index 812f833a..fa4e7d69 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -24,7 +24,7 @@ public class VMGCommand extends ObserverCommand { */ public VMGCommand(MockRace race, MockBoat boat) { super(race, boat); - race.addAngularCommand(this); + race.addAngularCommand(this, boat.getSourceID()); } @Override diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java index 85eec091..af2e422b 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java @@ -20,7 +20,7 @@ public class WindCommand extends ObserverCommand { */ public WindCommand(MockRace race, MockBoat boat, boolean upwind) { super(race, boat); - race.addAngularCommand(this); + race.addAngularCommand(this, boat.getSourceID()); this.direction = upwind? -1 : 1; } diff --git a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java index 2c15e57f..af520a83 100644 --- a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java +++ b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java @@ -56,13 +56,12 @@ public class RaceXMLCreator { * Rotates the race in a specified direction. * @param s xml file name or contents. * @param fileType Whether s is a file name or contents. - * @param degrees degrees to rotate - * @param tutorial Whether we wish to run the tutorial - this changes the race start time. + * @param degrees degrees to rotate. -1 means don't rotate. * @return the new xml file as a string * @throws XMLReaderException if the xml is not readable * @throws InvalidRaceDataException if the race is invalid */ - public static String alterRaceToWind(String s, XMLFileType fileType, double degrees, boolean tutorial) throws XMLReaderException, InvalidRaceDataException { + public static String alterRaceToWind(String s, XMLFileType fileType, double degrees) throws XMLReaderException, InvalidRaceDataException { RaceXMLReader reader = new RaceXMLReader(s, fileType); @@ -73,11 +72,6 @@ public class RaceXMLCreator { RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"), XMLRace.class); - if(tutorial){ - setRaceXMLAtCurrentTimeToNow(race, 1000l, 5000l); - } else { - setRaceXMLAtCurrentTimeToNow(race); - } CompoundMark leewardGate = getLeewardGate(reader); diff --git a/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java b/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java index e395e51f..3df46076 100644 --- a/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java +++ b/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java @@ -151,4 +151,9 @@ public class EmptyRaceDataSource implements RaceDataSource { public void incrementSequenceNumber() { sequenceNumber++; } + + @Override + public void setStartDateTime(ZonedDateTime time) { + raceStartTime = time; + } } diff --git a/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java b/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java index 765098f2..3b933329 100644 --- a/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java +++ b/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java @@ -67,6 +67,12 @@ public interface RaceDataSource { */ ZonedDateTime getStartDateTime(); + /** + * Sets the start time/date of the race. + * @param time Time to start at. + */ + void setStartDateTime(ZonedDateTime time); + /** * Returns the creation time/date of the race xml file. * @return The race xml file's creation time. diff --git a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java index af274a9c..0beac26d 100644 --- a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java @@ -507,4 +507,9 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { public void incrementSequenceNumber() { sequenceNumber++; } + + @Override + public void setStartDateTime(ZonedDateTime time) { + raceStartTime = time; + } } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java b/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java index 802117c2..9f30b448 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java @@ -10,6 +10,7 @@ import javafx.fxml.FXML; import javafx.geometry.Insets; import javafx.scene.Node; import javafx.scene.control.Alert; +import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.Label; import javafx.scene.image.ImageView; @@ -70,13 +71,10 @@ public class InGameLobbyController extends Controller { private Label playerLabel6; @FXML - private Label countdownLabel; + private Button startButton; @FXML - private AnchorPane countdownTenPane; - - @FXML - private Label countdownTenText; + private Button quitButton; private Event game; @@ -219,37 +217,13 @@ public class InGameLobbyController extends Controller { * Starts the race. */ private void startRace() { - //Initialises the race clock. - initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState()); //Starts the race countdown timer. countdownTimer(); } - /** - * Initialises the race clock/timer labels for the start time, current time, and remaining time. - * @param visualiserRace The race to get data from. - */ - private void initialiseRaceClock(VisualiserRaceState visualiserRace) { - //Remaining time. - initialiseRaceClockDuration(visualiserRace); - - } - - - /** - * Initialises the race duration label. - * @param visualiserRace The race to get data from. - */ - private void initialiseRaceClockDuration(VisualiserRaceState visualiserRace) { - visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> { - Platform.runLater(() -> { - countdownLabel.setText(newValue); - }); - }); - } /** * Countdown timer until race starts. */ @@ -260,19 +234,6 @@ public class InGameLobbyController extends Controller { //Get the current race status. RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum(); - //Try catch for getting interval times - try { - long interval = ChronoUnit.MILLIS.between(visualiserRaceEvent.getVisualiserRaceState().getRaceClock().getCurrentTime(), visualiserRaceEvent.getVisualiserRaceState().getRaceClock().getStartingTime()); - if(interval<=10000){ - countdownLabel.setVisible(false); - countdownTenPane.setVisible(true); - countdownTenText.setVisible(true); - } - countdownText(interval); - } catch (Exception e){ - - } - //If the race has reached the preparatory phase, or has started... if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) { @@ -311,6 +272,8 @@ public class InGameLobbyController extends Controller { this.visualiserRaceEvent.getVisualiserRaceState().getBoats().addListener(this.lobbyUpdateListener); + enableStartIfHost(); + startRace(); } catch (IOException e) { //TODO should probably let this propagate, so that we only enter this scene if everything works @@ -318,6 +281,22 @@ public class InGameLobbyController extends Controller { } } + + /** + * Enables the start button if the client is the host of the game. + */ + private void enableStartIfHost() { + if (isHost) { + startButton.setVisible(true); + startButton.setDisable(false); + } else { + startButton.setVisible(false); + startButton.setDisable(true); + } + } + + + /** * Menu button pressed. Prompt alert then return to menu * @throws IOException socket erro @@ -345,7 +324,7 @@ public class InGameLobbyController extends Controller { * Start button pressed. Currently only prints out start */ public void startBtnPressed(){ - //System.out.println("Should start the race. This button is only visible for the host"); + App.game.getRaceLogic().getRace().startRace(App.game.getRaceLogic().getRace().getRacePreparatoryTime(), true); } public void joinSpecPressed(){ @@ -356,48 +335,5 @@ public class InGameLobbyController extends Controller { //System.out.println("Empty race user pane pressed. Joining racers"); } - /** - * Countdown interval checker that updates the countdown text - * @param interval Countdown interval to check - */ - private void countdownText(long interval){ - countdownTenPane.setVisible(false); - //Do nothing if 5 seconds or less to go - if (interval <=5000){ - countdownTenPane.setVisible(false); - //countdownTenText.setText("5"); - return; - } - - //6 seconds left. Display 1 second for countdown - if (interval <=6000){ - countdownTenText.setText("1"); - return; - } - - //7 seconds left. Display 2 seconds for countdown - if (interval <=7000){ - countdownTenText.setText("2"); - return; - } - - //8 seconds left. Display 3 seconds for countdown - if (interval <=8000){ - countdownTenText.setText("3"); - return; - } - - //9 seconds left. Display 4 seconds for countdown - if (interval <=9000){ - countdownTenText.setText("4"); - return; - } - - //10 seconds left. Display 5 seconds for countdown - if (interval <=10000){ - countdownTenText.setText("5"); - return; - } - } } diff --git a/racevisionGame/src/main/resources/visualiser/scenes/gameLobby.fxml b/racevisionGame/src/main/resources/visualiser/scenes/gameLobby.fxml index 18658445..b0c5dc2e 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/gameLobby.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/gameLobby.fxml @@ -1,39 +1,23 @@ - - - - - - - - - - - - + - - @@ -82,6 +66,7 @@ +