From 27cf0e153994a084dd547c4176ac0dcd095e332f Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Wed, 2 Aug 2017 16:31:55 +1200 Subject: [PATCH 01/27] Started splitting MockRace into RaceLogic and RaceState. #refactor #story[1094] --- .../src/main/java/mock/model/MockRace.java | 10 - .../src/main/java/mock/model/RaceLogic.java | 73 +++ .../src/main/java/mock/model/RaceState.java | 156 ++++- .../src/main/java/mock/model/SplitTODO.java | 555 ++++++++++++++++++ .../src/main/java/shared/model/Race.java | 6 +- .../gameController/ControllerServer.java | 10 +- 6 files changed, 795 insertions(+), 15 deletions(-) create mode 100644 racevisionGame/src/main/java/mock/model/SplitTODO.java diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index fec34ad7..b9fb32be 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -33,14 +33,6 @@ public class MockRace extends Race { */ private List boats; - - - /** - * A copy of the boundary list, except "shrunk" inwards by 50m. - */ - private List shrinkBoundary; - - /** * The scale factor of the race. * See {@link Constants#RaceTimeScale}. @@ -69,8 +61,6 @@ public class MockRace extends Race { this.boats = this.generateMockBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), polars); - this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary); - //Set up wind generator. It may be tidier to create this outside the race (with the values sourced from a data file maybe?) and pass it in. this.windGenerator = new WindGenerator( Bearing.fromDegrees(225), diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index b8e904a6..0673e226 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -1,4 +1,77 @@ package mock.model; +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.LatestMessages; +import shared.dataInput.BoatDataSource; +import shared.dataInput.RaceDataSource; +import shared.dataInput.RegattaDataSource; +import shared.model.*; + +import java.util.Iterator; +import java.util.List; + public class RaceLogic { + private RaceState raceState; + + /** + * The scale factor of the race. + * See {@link Constants#RaceTimeScale}. + */ + private int scaleFactor; + + /** + * Object used to generate changes in wind speed/direction. + */ + private WindGenerator windGenerator; + + public RaceLogic(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars, int timeScale) { + this.raceState = new RaceState(boatDataSource, raceDataSource, regattaDataSource, latestMessages, polars); + this.raceState.run(); + + //Set up wind generator. It may be tidier to create this outside the race (with the values sourced from a data file maybe?) and pass it in. + this.windGenerator = new WindGenerator( + Bearing.fromDegrees(225), + Bearing.fromDegrees(215), + Bearing.fromDegrees(235), + 12d, + 8d, + 16d ); + raceState.setWind(windGenerator.generateBaselineWind()); + } + + private void changeWindDirection() { + Wind nextWind = windGenerator.generateNextWind(raceState.getWind()); + + raceState.setWind(nextWind); + } + + /** + * Returns the number of boats that are still active in the race. + * They become inactive by either finishing or withdrawing. + * @return The number of boats still active in the race. + */ + protected int getNumberOfActiveBoats() { + + int numberOfActiveBoats = 0; + + for (MockBoat boat : raceState.getBoats()) { + + //If the boat is currently racing, count it. + if (boat.getStatus() == BoatStatusEnum.RACING) { + numberOfActiveBoats++; + } + + } + + return numberOfActiveBoats; + } + + /** + * Returns a list of boats in the race. + * @return List of boats in the race. + */ + public List getBoats() { + return raceState.getBoats(); + } + } diff --git a/racevisionGame/src/main/java/mock/model/RaceState.java b/racevisionGame/src/main/java/mock/model/RaceState.java index 4b13cbb4..699417c5 100644 --- a/racevisionGame/src/main/java/mock/model/RaceState.java +++ b/racevisionGame/src/main/java/mock/model/RaceState.java @@ -1,4 +1,158 @@ package mock.model; -public class RaceState { +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.LatestMessages; +import shared.dataInput.BoatDataSource; +import shared.dataInput.RaceDataSource; +import shared.dataInput.RegattaDataSource; +import shared.model.*; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class RaceState extends Race { + + /** + * An observable list of boats in the race. + */ + private List boats; + + private Wind wind; + + public RaceState(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars) { + super(boatDataSource, raceDataSource, regattaDataSource, latestMessages); + this.boats = this.generateMockBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), polars); + } + + /** + * Generates a list of MockBoats given a list of Boats, and a list of participating boats. + * @param boats The map of Boats describing boats that are potentially in the race. Maps boat sourceID to boat. + * @param sourceIDs The list of boat sourceIDs describing which specific boats are actually participating. + * @param polars The polars table to be used for boat simulation. + * @return A list of MockBoats that are participating in the race. + */ + private List generateMockBoats(Map boats, List sourceIDs, Polars polars) { + + List mockBoats = new ArrayList<>(sourceIDs.size()); + + //For each sourceID participating... + for (int sourceID : sourceIDs) { + + //Get the boat associated with the sourceID. + Boat boat = boats.get(sourceID); + + //Construct a MockBoat using the Boat and Polars. + MockBoat mockBoat = new MockBoat(boat, polars); + + mockBoats.add(mockBoat); + + } + + return mockBoats; + + } + + /** + * Initialise the boats in the race. + * This sets their starting positions and current legs. + */ + @Override + protected void initialiseBoats() { + + //Gets the starting positions of the boats. + List startingPositions = getSpreadStartingPositions(); + + //Get iterators for our boat and position lists. + Iterator boatIt = this.boats.iterator(); + Iterator startPositionIt = startingPositions.iterator(); + + //Iterate over the pair of lists. + while (boatIt.hasNext() && startPositionIt.hasNext()) { + + //Get the next boat and position. + MockBoat boat = boatIt.next(); + GPSCoordinate startPosition = startPositionIt.next(); + + + //The boat starts on the first leg of the race. + boat.setCurrentLeg(this.legs.get(0)); + + //Boats start with 0 knots speed. + boat.setCurrentSpeed(0d); + + //Place the boat at its starting position. + boat.setCurrentPosition(startPosition); + + //Boats start facing their next marker. + boat.setBearing(boat.calculateBearingToNextMarker()); + + //Sets the boats status to prestart - it changes to racing when the race starts. + boat.setStatus(BoatStatusEnum.PRESTART); + + //We set a large time since tack change so that it calculates a new VMG when the simulation starts. + boat.setTimeSinceTackChange(999999); + + } + + } + + /** + * Creates a list of starting positions for the different boats, so they do not appear cramped at the start line. + * + * @return A list of starting positions. + */ + public List getSpreadStartingPositions() { + + //The first compound marker of the race - the starting gate. + CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark(); + + //The position of the two markers from the compound marker. + GPSCoordinate mark1Position = compoundMark.getMark1Position(); + GPSCoordinate mark2Position = compoundMark.getMark2Position(); + + + //Calculates the azimuth between the two points. + Azimuth azimuth = GPSCoordinate.calculateAzimuth(mark1Position, mark2Position); + + //Calculates the distance between the two points. + double distanceMeters = GPSCoordinate.calculateDistanceMeters(mark1Position, mark2Position); + + //The number of boats in the race. + int numberOfBoats = this.boats.size(); + + //Calculates the distance between each boat. We divide by numberOfBoats + 1 to ensure that no boat is placed on one of the starting gate's marks. + double distanceBetweenBoatsMeters = distanceMeters / (numberOfBoats + 1); + + + //List to store coordinates in. + List positions = new ArrayList<>(); + + //We start spacing boats out from mark 1. + GPSCoordinate position = mark1Position; + + //For each boat, displace position, and store it. + for (int i = 0; i < numberOfBoats; i++) { + + position = GPSCoordinate.calculateNewPosition(position, distanceBetweenBoatsMeters, azimuth); + + positions.add(position); + + } + + return positions; + } + + public void run() { + initialiseBoats(); + } + + public Wind getWind() { + return wind; + } + + public List getBoats() { + return boats; + } } diff --git a/racevisionGame/src/main/java/mock/model/SplitTODO.java b/racevisionGame/src/main/java/mock/model/SplitTODO.java new file mode 100644 index 00000000..20f14973 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/SplitTODO.java @@ -0,0 +1,555 @@ +//package mock.model; +// +//import javafx.animation.AnimationTimer; +//import network.Messages.BoatLocation; +//import network.Messages.BoatStatus; +//import network.Messages.Enums.BoatStatusEnum; +//import network.Messages.Enums.RaceStatusEnum; +//import network.Messages.LatestMessages; +//import network.Messages.RaceStatus; +//import network.Utils.AC35UnitConverter; +//import shared.dataInput.BoatDataSource; +//import shared.dataInput.RaceDataSource; +//import shared.dataInput.RegattaDataSource; +//import shared.model.*; +// +//import java.time.ZonedDateTime; +//import java.time.temporal.ChronoUnit; +//import java.util.ArrayList; +//import java.util.Iterator; +//import java.util.List; +//import java.util.Map; +// +//import static java.lang.Math.cos; +// +///** +// * Unused class, copy of MockRace so methods can be deleted once they are moved (more of a checklist) +// */ +//public class SplitTODO { +// +// +// /** +// * Represents a yacht race. +// * Has a course, boats, boundaries, etc... +// * Is responsible for simulating the race, and sending messages to a MockOutput instance. +// */ +// public class MockRace extends Race { +// +// /** +// * 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). +// * @param raceDataSource Data source for race related data (participating boats, legs, etc...). +// * @param regattaDataSource Data source for race related data (course name, location, timezone, etc...). +// * @param latestMessages The LatestMessages to send events to. +// * @param polars The polars table to be used for boat simulation. +// * @param timeScale The timeScale for the race. See {@link Constants#RaceTimeScale}. +// */ +// public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars, int timeScale) { +// +// super(boatDataSource, raceDataSource, regattaDataSource, latestMessages); +// +// this.scaleFactor = timeScale; +// } +// +// +// +// /** +// * Parse the compound marker boats through mock output. +// */ +// private void parseMarks() { +// for (CompoundMark compoundMark : this.compoundMarks) { +// +// //Get the individual marks from the compound mark. +// Mark mark1 = compoundMark.getMark1(); +// Mark mark2 = compoundMark.getMark2(); +// +// //If they aren't null, parse them (some compound marks only have one mark). +// if (mark1 != null) { +// this.parseIndividualMark(mark1); +// } +// +// if (mark2 != null) { +// this.parseIndividualMark(mark2); +// } +// +// } +// } +// +// /** +// * Parses an individual marker boat, and sends it to mockOutput. +// * @param mark The marker boat to parse. +// */ +// private void parseIndividualMark(Mark mark) { +// +// //Create message. +// BoatLocation boatLocation = new BoatLocation( +// mark.getSourceID(), +// mark.getPosition().getLatitude(), +// mark.getPosition().getLongitude(), +// this.boatLocationSequenceNumber, +// 0, 0, +// this.raceClock.getCurrentTimeMilli()); +// +// //Iterates the sequence number. +// this.boatLocationSequenceNumber++; +// +// this.latestMessages.setBoatLocation(boatLocation); +// +// +// } +// +// /** +// * Parse the boats in the race, and send it to mockOutput. +// */ +// private void parseBoatLocations() { +// +// //Parse each boat. +// for (MockBoat boat : this.boats) { +// +// this.parseIndividualBoatLocation(boat); +// +// } +// +// } +// +// /** +// * Parses an individual boat, and sends it to mockOutput. +// * @param boat The boat to parse. +// */ +// private void parseIndividualBoatLocation(MockBoat boat) { +// +// BoatLocation boatLocation = new BoatLocation( +// boat.getSourceID(), +// boat.getCurrentPosition().getLatitude(), +// boat.getCurrentPosition().getLongitude(), +// this.boatLocationSequenceNumber, +// boat.getBearing().degrees(), +// boat.getCurrentSpeed(), +// this.raceClock.getCurrentTimeMilli()); +// +// //Iterates the sequence number. +// this.boatLocationSequenceNumber++; +// +// this.latestMessages.setBoatLocation(boatLocation); +// +// } +// +// +// /** +// * Updates the race time to a specified value, in milliseconds since the unix epoch. +// * @param currentTime Milliseconds since unix epoch. +// */ +// private void updateRaceTime(long currentTime) { +// this.raceClock.setUTCTime(currentTime); +// } +// +// +// /** +// * Updates the race status enumeration based on the current time. +// */ +// private void updateRaceStatusEnum() { +// +// //The millisecond duration of the race. Negative means it hasn't started, so we flip sign. +// long timeToStart = - this.raceClock.getDurationMilli(); +// +// +// if (timeToStart > Constants.RacePreStartTime) { +// //Time > 3 minutes is the prestart period. +// this.setRaceStatusEnum(RaceStatusEnum.PRESTART); +// +// } else if ((timeToStart <= Constants.RacePreStartTime) && (timeToStart >= Constants.RacePreparatoryTime)) { +// //Time between [1, 3] minutes is the warning period. +// this.setRaceStatusEnum(RaceStatusEnum.WARNING); +// +// } else if ((timeToStart <= Constants.RacePreparatoryTime) && (timeToStart > 0)) { +// //Time between (0, 1] minutes is the preparatory period. +// this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY); +// +// } else { +// //Otherwise, the race has started! +// this.setRaceStatusEnum(RaceStatusEnum.STARTED); +// +// } +// +// +// } +// +// /** +// * Parses the race status, and sends it to mockOutput. +// */ +// private void parseRaceStatus() { +// +// //A race status message contains a list of boat statuses. +// List boatStatuses = new ArrayList<>(); +// +// //Add each boat status to the status list. +// for (MockBoat boat : this.boats) { +// +// BoatStatus boatStatus = new BoatStatus( +// boat.getSourceID(), +// boat.getStatus(), +// boat.getCurrentLeg().getLegNumber(), +// boat.getEstimatedTimeAtNextMark().toInstant().toEpochMilli() ); +// +// boatStatuses.add(boatStatus); +// } +// +// +// //Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class. +// int windDirectionInt = AC35UnitConverter.encodeHeading(this.getWindDirection().degrees()); +// int windSpeedInt = (int) (this.getWindSpeed() * Constants.KnotsToMMPerSecond); +// +// //Create race status object, and send it. +// RaceStatus raceStatus = new RaceStatus( +// System.currentTimeMillis(), +// this.raceId, +// this.getRaceStatusEnum().getValue(), +// this.raceClock.getStartingTimeMilli(), +// windDirectionInt, +// windSpeedInt, +// this.getRaceType().getValue(), +// boatStatuses); +// +// +// this.latestMessages.setRaceStatus(raceStatus); +// +// +// } +// +// +// /** +// * Sets the status of all boats in the race to RACING. +// */ +// private void setBoatsStatusToRacing() { +// +// for (MockBoat boat : this.boats) { +// boat.setStatus(BoatStatusEnum.RACING); +// } +// } +// +// +// /** +// * Sets the estimated time at next mark for each boat to a specified time. This is used during the countdown timer to provide this value to boat before the race starts. +// * @param time The time to provide to each boat. +// */ +// private void setBoatsTimeNextMark(ZonedDateTime time) { +// +// for (MockBoat boat : this.boats) { +// boat.setEstimatedTimeAtNextMark(time); +// } +// } +// +// +// /** +// * Countdown timer until race starts. +// */ +// protected AnimationTimer countdownTimer = new AnimationTimer() { +// +// +// long currentTime = System.currentTimeMillis(); +// +// @Override +// public void handle(long arg0) { +// +// //Update race time. +// updateRaceTime(currentTime); +// +// //Update the race status based on the current time. +// updateRaceStatusEnum(); +// +// //Provide boat's with an estimated time at next mark until the race starts. +// setBoatsTimeNextMark(raceClock.getCurrentTime()); +// +// //Parse the boat locations. +// parseBoatLocations(); +// +// //Parse the marks. +// parseMarks(); +// +// // Change wind direction +// changeWindDirection(); +// +// //Parse the race status. +// parseRaceStatus(); +// +// +// if (getRaceStatusEnum() == RaceStatusEnum.STARTED) { +// setBoatsStatusToRacing(); +// raceTimer.start(); +// this.stop(); +// } +// +// //Update the animations timer's time. +// currentTime = System.currentTimeMillis(); +// } +// }; +// +// +// /** +// * Timer that runs for the duration of the race, until all boats finish. +// */ +// private AnimationTimer raceTimer = new AnimationTimer() { +// +// /** +// * Start time of loop, in milliseconds. +// */ +// long timeRaceStarted = System.currentTimeMillis(); +// +// /** +// * Current time during a loop iteration. +// */ +// long currentTime = System.currentTimeMillis(); +// +// /** +// * The time of the previous frame, in milliseconds. +// */ +// long lastFrameTime = timeRaceStarted; +// +// @Override +// public void handle(long arg0) { +// +// //Get the current time. +// currentTime = System.currentTimeMillis(); +// +// //Update race time. +// updateRaceTime(currentTime); +// +// +// //As long as there is at least one boat racing, we still simulate the race. +// if (getNumberOfActiveBoats() != 0) { +// +// //Get the time period of this frame. +// long framePeriod = currentTime - lastFrameTime; +// +// //For each boat, we update its position, and generate a BoatLocationMessage. +// for (MockBoat boat : boats) { +// +// //If it is still racing, update its position. +// if (boat.getStatus() == BoatStatusEnum.RACING) { +// +// updatePosition(boat, framePeriod, raceClock.getDurationMilli()); +// +// } +// +// } +// +// } else { +// //Otherwise, the race is over! +// raceFinished.start(); +// setRaceStatusEnum(RaceStatusEnum.FINISHED); +// this.stop(); +// } +// +// if (getNumberOfActiveBoats() != 0) { +// // Change wind direction +// changeWindDirection(); +// +// //Parse the boat locations. +// parseBoatLocations(); +// +// //Parse the marks. +// parseMarks(); +// +// //Parse the race status. +// parseRaceStatus(); +// +// +// //Update the last frame time. +// this.lastFrameTime = currentTime; +// } +// } +// }; +// +// /** +// * Broadcast that the race has finished. +// */ +// protected AnimationTimer raceFinished = new AnimationTimer(){ +// int iters = 0; +// @Override +// public void handle(long now) { +// +// parseRaceStatus(); +// +// if (iters > 500) { +// stop(); +// } +// iters++; +// } +// }; +// +// +// /** +// * Calculates a boat's VMG. +// * @param boat The boat to calculate VMG for. +// * @return VMG for the specified boat. +// */ +// private VMG calculateVMG(MockBoat boat) { +// +// +// //Find the VMG inside these bounds. +// VMG bestVMG = boat.getPolars().calculateVMG(this.getWindDirection(), this.getWindSpeed(), boat.calculateBearingToNextMarker(), Bearing.fromDegrees(0d), Bearing.fromDegrees(359.99999d)); +// +// +// return bestVMG; +// +// } +// +// +// /** +// * Determines whether or not a given VMG improves the velocity of a boat, if it were currently using currentVMG. +// * @param currentVMG The current VMG of the boat. +// * @param potentialVMG The new VMG to test. +// * @param bearingToDestination The bearing between the boat and its destination. +// * @return True if the new VMG is improves velocity, false otherwise. +// */ +// private boolean improvesVelocity(VMG currentVMG, VMG potentialVMG, Bearing bearingToDestination) { +// +// //Calculates the angle between the boat and its destination. +// Angle angleBetweenDestAndHeading = Angle.fromDegrees(currentVMG.getBearing().degrees() - bearingToDestination.degrees()); +// +// //Calculates the angle between the new VMG and the boat's destination. +// Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(potentialVMG.getBearing().degrees() - bearingToDestination.degrees()); +// +// +// //Calculate the boat's current velocity. +// double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * currentVMG.getSpeed(); +// +// //Calculate the potential velocity with the new VMG. +// double vmgVelocity = Math.cos(angleBetweenDestAndNewVMG.radians()) * potentialVMG.getSpeed(); +// +// //Return whether or not the new VMG gives better velocity. +// return vmgVelocity > currentVelocity; +// +// } +// +// /** +// * Determines whether or not a given VMG improves the velocity of a boat. +// * @param boat The boat to test. +// * @param vmg The new VMG to test. +// * @return True if the new VMG is improves velocity, false otherwise. +// */ +// private boolean improvesVelocity(MockBoat boat, VMG vmg) { +// +// //Get the boats "current" VMG. +// VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing()); +// +// //Check if the new VMG is better than the boat's current VMG. +// return this.improvesVelocity(boatVMG, vmg, boat.calculateBearingToNextMarker()); +// +// } +// +// +// /** +// * Calculates the distance a boat has travelled and updates its current position according to this value. +// * +// * @param boat The boat to be updated. +// * @param updatePeriodMilliseconds The time, in milliseconds, since the last update. +// * @param totalElapsedMilliseconds The total number of milliseconds that have elapsed since the start of the race. +// */ +// protected void updatePosition(MockBoat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) { +// +// //Checks if the current boat has finished the race or not. +// boolean finish = this.isLastLeg(boat.getCurrentLeg()); +// +// if (!finish) { +// +// +// //Calculates the distance travelled, in meters, in the current timeslice. +// double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds); +// +// //Scale it. +// distanceTravelledMeters = distanceTravelledMeters * this.scaleFactor; +// +// +// //Move the boat forwards that many meters, and advances its time counters by enough milliseconds. +// boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor); +// +// long tackPeriod = 15000; +// if (boat.getTimeSinceTackChange() > tackPeriod) { +// //Calculate the new VMG. +// VMG newVMG = this.calculateVMG(boat); +// +// +// //If the new vmg improves velocity, use it. +// if (improvesVelocity(boat, newVMG)) { +// boat.setVMG(newVMG); +// +// } +// } +// +// this.updateEstimatedTime(boat); +// +// +// //Check the boats position (update leg and stuff). +// this.checkPosition(boat, totalElapsedMilliseconds); +// +// } +// +// } +// +// +// /** +// * Checks if a boat has finished any legs, or has pulled out of race (DNF). +// * @param boat The boat to check. +// * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. +// */ +// protected void checkPosition(MockBoat boat, long timeElapsed) { +// +// //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. +// double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. +// +// if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) { +// //Boat has reached its target marker, and has moved on to a new leg. +// +// +// +// //Calculate how much the boat overshot the marker by. +// double overshootMeters = boat.calculateDistanceToNextMarker(); +// +// +// //Move boat on to next leg. +// Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); +// boat.setCurrentLeg(nextLeg); +// +// //Add overshoot distance into the distance travelled for the next leg. +// boat.setDistanceTravelledInLeg(overshootMeters); +// +// //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark. +// boat.setTimeSinceTackChange(999999); +// +// +// //Check if the boat has finished or stopped racing. +// +// if (this.isLastLeg(boat.getCurrentLeg())) { +// //Boat has finished. +// boat.setTimeFinished(timeElapsed); +// boat.setCurrentSpeed(0); +// boat.setStatus(BoatStatusEnum.FINISHED); +// +// } +// +// } +// +// } +// +// /** +// * Updates the boat's estimated time to next mark if positive +// * @param boat to estimate time given its velocity +// */ +// private void updateEstimatedTime(MockBoat boat) { +// +// double velocityToMark = boat.getCurrentSpeed() * cos(boat.getBearing().radians() - boat.calculateBearingToNextMarker().radians()) / Constants.KnotsToMMPerSecond; +// +// if (velocityToMark > 0) { +// +// //Calculate milliseconds until boat reaches mark. +// long timeFromNow = (long) (1000 * boat.calculateDistanceToNextMarker() / velocityToMark); +// +// //Calculate time at which it will reach mark. +// ZonedDateTime timeAtMark = this.raceClock.getCurrentTime().plus(timeFromNow, ChronoUnit.MILLIS); +// boat.setEstimatedTimeAtNextMark(timeAtMark); +// } +// +// } +// } +//} diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java index aec57882..3235f60f 100644 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -226,7 +226,7 @@ public abstract class Race implements Runnable { * Sets the current race status. * @param raceStatusEnum The new status of the race. */ - protected void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) { + public void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) { this.raceStatusEnum = raceStatusEnum; } @@ -253,7 +253,7 @@ public abstract class Race implements Runnable { * @param windBearing New wind bearing. * @param windSpeedKnots New wind speed, in knots. */ - protected void setWind(Bearing windBearing, double windSpeedKnots) { + public void setWind(Bearing windBearing, double windSpeedKnots) { Wind wind = new Wind(windBearing, windSpeedKnots); setWind(wind); } @@ -262,7 +262,7 @@ public abstract class Race implements Runnable { * Updates the race to have a specified wind (bearing and speed). * @param wind New wind. */ - protected void setWind(Wind wind) { + public void setWind(Wind wind) { this.raceWind.setValue(wind); } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index fb6a257b..dc3f3a03 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -9,6 +9,7 @@ import visualiser.gameController.Keys.KeyFactory; import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; +import java.util.Queue; /** * Service for dispatching key press data to race from client @@ -23,6 +24,9 @@ public class ControllerServer implements Runnable { */ private DataInputStream inputStream; + // Last boat action received + private Queue boatActions; + /** * Initialise server-side controller with live client socket * @param socket to client @@ -49,11 +53,15 @@ public class ControllerServer implements Runnable { BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message); BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody()); BoatActionEnum decodedMessage = boatActionDecoder.getBoatAction(); - System.out.println("Received key: " + decodedMessage); + boatActions.add(decodedMessage); } } catch (IOException e) { e.printStackTrace(); } } } + + public BoatActionEnum getNextBoatAction() { + return boatActions.remove(); + } } From c633de21f50f5def04c438a62ebd3c15831cf574 Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 2 Aug 2017 16:33:03 +1200 Subject: [PATCH 02/27] added new methods to the mock boats to let them check which side a mark is on to them #story[1101] --- .../src/main/java/mock/model/MockBoat.java | 24 +++ .../test/java/mock/model/MockBoatTest.java | 153 +++++++++++++++++- 2 files changed, 175 insertions(+), 2 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index c8c6825b..05efd0cf 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -203,4 +203,28 @@ public class MockBoat extends Boat { return distanceTravelledMeters; } + /** + * Check if a mark is on the port side of the boat + */ + public boolean isPortSide(Mark mark){ + //if this boat is lower than the mark check which way it is facing + if(this.getCurrentPosition().getLongitude() < mark.getPosition().getLongitude()){ + return this.getBearing().degrees() <= 180; + }else{ + return this.getBearing().degrees() > 180; + } + } + + /** + * Check if a mark is on the starboard side of the boat + */ + public boolean isStarboardSide(Mark mark){ + //if this boat is lower than the mark check which way it is facing + if(this.getCurrentPosition().getLongitude() < mark.getPosition().getLongitude()){ + return this.getBearing().degrees() >= 180; + }else{ + return this.getBearing().degrees() < 180; + } + } + } diff --git a/racevisionGame/src/test/java/mock/model/MockBoatTest.java b/racevisionGame/src/test/java/mock/model/MockBoatTest.java index b1ee551c..71b8f715 100644 --- a/racevisionGame/src/test/java/mock/model/MockBoatTest.java +++ b/racevisionGame/src/test/java/mock/model/MockBoatTest.java @@ -1,7 +1,156 @@ package mock.model; -import static org.junit.Assert.*; +import mock.dataInput.PolarParser; +import mock.exceptions.InvalidPolarFileException; +import org.junit.Before; +import org.junit.Test; +import shared.model.Bearing; +import shared.model.GPSCoordinate; +import shared.model.Mark; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class MockBoatTest { -//TODO + + /** + * boat made for testing + */ + private MockBoat firstTestBoat; + + private Mark markToTest; + + private GPSCoordinate highGPS; + private GPSCoordinate middleGPS; + private GPSCoordinate lowGPS; + + /** + * Creates the Polars object for the tests. + */ + @Before + public void setUp() { + //Read in polars. + try { + //Parse data file. + Polars polars = PolarParser.parse("mock/polars/acc_polars.csv"); + + firstTestBoat = new MockBoat(1, "test", "NZ", polars); + highGPS = new GPSCoordinate(100, 100); + middleGPS = new GPSCoordinate(100, 75); + lowGPS = new GPSCoordinate(100, 50); + markToTest = new Mark(1, "test MARK", middleGPS); + } + catch (InvalidPolarFileException e) { + fail("Couldn't parse polar file."); + } + } + + //////////////////////////////Mark Higher//////////////////////////////// + + /** + * Tests if the boat is lower than the mark that the port side method works if + * boat is facing east + */ + @Test + public void testIsPortSide() { + firstTestBoat.setBearing(Bearing.fromDegrees(90)); + firstTestBoat.setCurrentPosition(lowGPS); + markToTest.setPosition(highGPS); + + assertEquals(firstTestBoat.isPortSide(markToTest), true); + } + + /** + * Tests if the boat is lower than the mark that the port side method works if + * boat is facing west + */ + @Test + public void testIsPortSideWrong() { + firstTestBoat.setBearing(Bearing.fromDegrees(270)); + firstTestBoat.setCurrentPosition(lowGPS); + markToTest.setPosition(highGPS); + + assertEquals(firstTestBoat.isPortSide(markToTest), false); + } + + /** + * Tests if the boat is lower than the mark that the starboard side method works if + * boat is facing east + */ + @Test + public void testIsStarboardSideWrong() { + firstTestBoat.setBearing(Bearing.fromDegrees(90)); + firstTestBoat.setCurrentPosition(lowGPS); + markToTest.setPosition(highGPS); + + assertEquals(firstTestBoat.isStarboardSide(markToTest), false); + } + + /** + * Tests if the boat is lower than the mark that the starboard side method works if + * boat is facing west + */ + @Test + public void testIsStarboardSide() { + firstTestBoat.setBearing(Bearing.fromDegrees(270)); + firstTestBoat.setCurrentPosition(lowGPS); + markToTest.setPosition(highGPS); + + assertEquals(firstTestBoat.isStarboardSide(markToTest), true); + } + + + //////////////////////////////Mark Lower//////////////////////////////// + + /** + * Tests if the boat is higher than the mark that the port side method works if + * boat is facing east + */ + @Test + public void testIsPortSideHigherWrong() { + firstTestBoat.setBearing(Bearing.fromDegrees(90)); + firstTestBoat.setCurrentPosition(highGPS); + markToTest.setPosition(lowGPS); + + assertEquals(firstTestBoat.isPortSide(markToTest), false); + } + + /** + * Tests if the boat is higher than the mark that the port side method works if + * boat is facing west + */ + @Test + public void testIsPortSideHigher() { + firstTestBoat.setBearing(Bearing.fromDegrees(270)); + firstTestBoat.setCurrentPosition(highGPS); + markToTest.setPosition(lowGPS); + + assertEquals(firstTestBoat.isPortSide(markToTest), true); + } + + /** + * Tests if the boat is higher than the mark that the starboard side method works if + * boat is facing east + */ + @Test + public void testIsStarboardSideHigher() { + firstTestBoat.setBearing(Bearing.fromDegrees(90)); + firstTestBoat.setCurrentPosition(highGPS); + markToTest.setPosition(lowGPS); + + assertEquals(firstTestBoat.isStarboardSide(markToTest), true); + } + + /** + * Tests if the boat is higher than the mark that the starboard side method works if + * boat is facing west + */ + @Test + public void testIsStarboardSideHigherWrong() { + firstTestBoat.setBearing(Bearing.fromDegrees(270)); + firstTestBoat.setCurrentPosition(highGPS); + markToTest.setPosition(lowGPS); + + assertEquals(firstTestBoat.isStarboardSide(markToTest), false); + } } From 13922bc2841591178c95602159ca8d9cf88e0b03 Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 2 Aug 2017 17:06:52 +1200 Subject: [PATCH 03/27] updated javadoc #story[1101] --- racevisionGame/src/main/java/mock/model/MockBoat.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index 05efd0cf..44e8e4e6 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -205,6 +205,7 @@ public class MockBoat extends Boat { /** * Check if a mark is on the port side of the boat + * @param mark mark to be passed */ public boolean isPortSide(Mark mark){ //if this boat is lower than the mark check which way it is facing @@ -217,6 +218,7 @@ public class MockBoat extends Boat { /** * Check if a mark is on the starboard side of the boat + * @param mark mark to be passed */ public boolean isStarboardSide(Mark mark){ //if this boat is lower than the mark check which way it is facing From b7af4e19cfcfb4ce56776b8c39b802f79df2cf2f Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 2 Aug 2017 18:09:12 +1200 Subject: [PATCH 04/27] new method to check if a boat is between gates as well as updated the gps values to fit better with real life values #story[1101] --- .../src/main/java/mock/model/MockBoat.java | 16 +++++++++++++ .../test/java/mock/model/MockBoatTest.java | 24 ++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index 44e8e4e6..97fe68ef 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -206,6 +206,7 @@ public class MockBoat extends Boat { /** * Check if a mark is on the port side of the boat * @param mark mark to be passed + * @return true if mark is on port side */ public boolean isPortSide(Mark mark){ //if this boat is lower than the mark check which way it is facing @@ -219,6 +220,7 @@ public class MockBoat extends Boat { /** * Check if a mark is on the starboard side of the boat * @param mark mark to be passed + * @return true if mark is on starboard side */ public boolean isStarboardSide(Mark mark){ //if this boat is lower than the mark check which way it is facing @@ -229,4 +231,18 @@ public class MockBoat extends Boat { } } + /** + * Used to check if this boat is between a gate + * @param gate the gate to be checked + * @return true if the boat is between two marks that make up a gate + */ + public boolean isBetweenGate(CompoundMark gate){ + if ((this.isPortSide(gate.getMark1()) && this.isStarboardSide(gate.getMark2())) || + (this.isStarboardSide(gate.getMark2()) && this.isPortSide(gate.getMark1()))){ + return true; + }else{ + return false; + } + } + } diff --git a/racevisionGame/src/test/java/mock/model/MockBoatTest.java b/racevisionGame/src/test/java/mock/model/MockBoatTest.java index 71b8f715..32c28ce1 100644 --- a/racevisionGame/src/test/java/mock/model/MockBoatTest.java +++ b/racevisionGame/src/test/java/mock/model/MockBoatTest.java @@ -5,6 +5,7 @@ import mock.exceptions.InvalidPolarFileException; import org.junit.Before; import org.junit.Test; import shared.model.Bearing; +import shared.model.CompoundMark; import shared.model.GPSCoordinate; import shared.model.Mark; @@ -19,6 +20,7 @@ public class MockBoatTest { private MockBoat firstTestBoat; private Mark markToTest; + private Mark markToTest2; private GPSCoordinate highGPS; private GPSCoordinate middleGPS; @@ -35,10 +37,11 @@ public class MockBoatTest { Polars polars = PolarParser.parse("mock/polars/acc_polars.csv"); firstTestBoat = new MockBoat(1, "test", "NZ", polars); - highGPS = new GPSCoordinate(100, 100); - middleGPS = new GPSCoordinate(100, 75); - lowGPS = new GPSCoordinate(100, 50); + highGPS = new GPSCoordinate(-64.854000, 32.296577); + middleGPS = new GPSCoordinate(-64.854000, 32.292500); + lowGPS = new GPSCoordinate(-64.854000, 32.290000); markToTest = new Mark(1, "test MARK", middleGPS); + markToTest2 = new Mark(2, "test MARK2", middleGPS); } catch (InvalidPolarFileException e) { fail("Couldn't parse polar file."); @@ -153,4 +156,19 @@ public class MockBoatTest { assertEquals(firstTestBoat.isStarboardSide(markToTest), false); } + + /** + * Tests if a boat is between a gate + */ + @Test + public void testIsBetweenGate(){ + markToTest.setPosition(highGPS); + markToTest2.setPosition(lowGPS); + CompoundMark testGate = new CompoundMark(1, "test GATE", markToTest, markToTest2); + + firstTestBoat.setCurrentPosition(middleGPS); + + assertEquals(firstTestBoat.isBetweenGate(testGate), true); + + } } From f212414bd99f5c4273e945fb9b325799e6ef6e40 Mon Sep 17 00:00:00 2001 From: hba56 Date: Thu, 3 Aug 2017 13:27:00 +1200 Subject: [PATCH 05/27] Added in a new basis for boats to round marks, gave mockboats a status to say how far through a rounding they are and made a method in GPScoordinate public so it can be used to calculate intersections. This branch will not run the game any more as boats can't move on to the next leg until they can be controlled by the user. #story[1087] --- .../src/main/java/mock/model/MockBoat.java | 19 ++++ .../src/main/java/mock/model/MockRace.java | 105 +++++++++++++++--- .../main/java/shared/model/GPSCoordinate.java | 2 +- 3 files changed, 110 insertions(+), 16 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index 97fe68ef..2874aa30 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -22,6 +22,14 @@ public class MockBoat extends Boat { */ private long timeSinceTackChange = 0; + /** + * This stores the boats current status of rounding a mark + * 0: not started rounding + * 1: passed only first check + * 2: passed first and second check + */ + private Integer roundingStatus; + /** @@ -245,4 +253,15 @@ public class MockBoat extends Boat { } } + public Integer getRoundingStatus() { + return Integer.valueOf(roundingStatus); + } + + public void increaseRoundingStatus() { + this.roundingStatus++; + } + + public void resetRoundingStatus() { + this.roundingStatus = 0; + } } diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index ad259bdc..3c394767 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -771,39 +771,60 @@ public class MockRace extends Race { } - /** * Checks if a boat has finished any legs, or has pulled out of race (DNF). * @param boat The boat to check. * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. */ - protected void checkPosition(MockBoat boat, long timeElapsed) { + protected void checkPosition(MockBoat boat, long timeElapsed) {//TODO cater for gates and rounding that aren't port side //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. - double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. + double epsilonNauticalMiles = 250.0 / Constants.NMToMetersConversion; //250 meters. if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) { - //Boat has reached its target marker, and has moved on to a new leg. - + //Boat is within an acceptable distance from the mark. + GPSCoordinate startDirectionLinePoint = boat.getCurrentLeg().getStartCompoundMark().getMark1Position(); + //todo will need to change this for gates, so that the end point is the side of the gate needed to be rounded + GPSCoordinate endDirectionLinePoint = boat.getCurrentLeg().getEndCompoundMark().getMark1Position(); - //Calculate how much the boat overshot the marker by. - double overshootMeters = boat.calculateDistanceToNextMarker(); + Bearing bearingOfDirectionLine = GPSCoordinate.calculateBearing(startDirectionLinePoint, endDirectionLinePoint); + //use the direction line to create three invisible points that act as crossover lines a boat must cross + //to round a mark + GPSCoordinate roundCheck1 = GPSCoordinate.calculateNewPosition(startDirectionLinePoint, + epsilonNauticalMiles, Azimuth.fromDegrees(bearingOfDirectionLine.degrees() + 90));//adding 90 so the check line is parallel - //Move boat on to next leg. - Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); - boat.setCurrentLeg(nextLeg); + GPSCoordinate roundCheck2 = GPSCoordinate.calculateNewPosition(startDirectionLinePoint, + epsilonNauticalMiles, Azimuth.fromDegrees(bearingOfDirectionLine.degrees())); - //Add overshoot distance into the distance travelled for the next leg. - boat.setDistanceTravelledInLeg(overshootMeters); - //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark. - boat.setTimeSinceTackChange(999999); + //boats must pass all checks in order to round a mark + switch (boat.getRoundingStatus()){ + case 0://hasn't started rounding + if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && + GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), + roundCheck1, boat.getCurrentPosition())){ + boat.increaseRoundingStatus(); + } + break; + case 1://has been parallel to the mark + if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && + GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), + roundCheck2, boat.getCurrentPosition())){ + boat.increaseRoundingStatus(); + } + break; + case 2://has traveled 180 degrees around the mark + //Move boat on to next leg. + boat.resetRoundingStatus(); + Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); + boat.setCurrentLeg(nextLeg); + break; + } //Check if the boat has finished or stopped racing. - if (this.isLastLeg(boat.getCurrentLeg())) { //Boat has finished. boat.setTimeFinished(timeElapsed); @@ -824,6 +845,60 @@ public class MockRace extends Race { } +//old method fo checking if boats passed a mark +// +// /** +// * Checks if a boat has finished any legs, or has pulled out of race (DNF). +// * @param boat The boat to check. +// * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. +// */ +// protected void checkPosition(MockBoat boat, long timeElapsed) { +// +// //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. +// double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. +// +// if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) { +// //Boat has reached its target marker, and has moved on to a new leg. +// +// +// +// //Calculate how much the boat overshot the marker by. +// double overshootMeters = boat.calculateDistanceToNextMarker(); +// +// +// //Move boat on to next leg. +// Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); +// boat.setCurrentLeg(nextLeg); +// +// //Add overshoot distance into the distance travelled for the next leg. +// boat.setDistanceTravelledInLeg(overshootMeters); +// +// //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark. +// boat.setTimeSinceTackChange(999999); +// +// +// //Check if the boat has finished or stopped racing. +// +// if (this.isLastLeg(boat.getCurrentLeg())) { +// //Boat has finished. +// boat.setTimeFinished(timeElapsed); +// boat.setCurrentSpeed(0); +// boat.setStatus(BoatStatusEnum.FINISHED); +// +// } else if (doNotFinish()) { +// //Boat has pulled out of race. +// boat.setTimeFinished(timeElapsed); +// boat.setCurrentLeg(new Leg("DNF", -1)); +// boat.setCurrentSpeed(0); +// boat.setStatus(BoatStatusEnum.DNF); +// +// } +// +// } +// +// } + + /** diff --git a/racevisionGame/src/main/java/shared/model/GPSCoordinate.java b/racevisionGame/src/main/java/shared/model/GPSCoordinate.java index ee5eaaff..566413c6 100644 --- a/racevisionGame/src/main/java/shared/model/GPSCoordinate.java +++ b/racevisionGame/src/main/java/shared/model/GPSCoordinate.java @@ -142,7 +142,7 @@ public class GPSCoordinate { * @param coordinate The coordinate to test. * @return true if a line from the point intersects the two boundary points */ - private static boolean intersects(GPSCoordinate boundaryA, GPSCoordinate boundaryB, GPSCoordinate coordinate) { + public static boolean intersects(GPSCoordinate boundaryA, GPSCoordinate boundaryB, GPSCoordinate coordinate) { double boundaryALat = boundaryA.getLatitude(); double boundaryALon = boundaryA.getLongitude(); double boundaryBLat = boundaryB.getLatitude(); From 14ce5fcaffbf773d66719ab3640ca5dfd006dcaf Mon Sep 17 00:00:00 2001 From: zwu18 Date: Fri, 4 Aug 2017 03:56:55 +1200 Subject: [PATCH 06/27] Made changes to TackGybeCommand and VMGCommand classes to fit pattern layout. #Story[1097] --- .../src/main/java/mock/model/MockRace.java | 4 ++-- .../model/commandFactory/CommandFactory.java | 2 +- .../model/commandFactory/TackGybeCommand.java | 11 ++++++++++- .../mock/model/commandFactory/VMGCommand.java | 17 +++++++++-------- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index bfcf5efc..81042828 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -261,7 +261,6 @@ public class MockRace extends Race { //For each boat, we update its position, and generate a BoatLocationMessage. for (MockBoat boat : boats) { - //If it is still racing, update its position. if (boat.getStatus() == BoatStatusEnum.RACING) { @@ -417,6 +416,7 @@ public class MockRace extends Race { */ public boolean improvesVelocity(VMG currentVMG, VMG potentialVMG, Bearing bearingToDestination) { + //Calculates the angle between the boat and its destination. Angle angleBetweenDestAndHeading = Angle.fromDegrees(currentVMG.getBearing().degrees() - bearingToDestination.degrees()); @@ -478,7 +478,7 @@ public class MockRace extends Race { boat.moveForwards(distanceTravelledMeters); - long tackPeriod = 15000; + long tackPeriod = 1000; if (boat.getTimeSinceTackChange() > tackPeriod) { //Calculate the new VMG. diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java index ace2f3be..8a53547c 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -5,5 +5,5 @@ import mock.model.MockRace; public interface CommandFactory { - void runCommand(MockBoat boat, MockRace race); + void execute(); } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index ef776536..3a7dc0eb 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -11,8 +11,15 @@ import shared.model.Bearing; public class TackGybeCommand implements CommandFactory { //The refactoring of MockRace will require changes to be made + + private MockBoat boat; + + public TackGybeCommand(final MockBoat boat){ + this.boat = boat; + } + @Override - public void runCommand(MockBoat boat, MockRace race) { + public void execute() { /*VMG newVMG = boat.getPolars().calculateVMG( race.getWindDirection(), race.getWindSpeed(), @@ -23,5 +30,7 @@ public class TackGybeCommand implements CommandFactory { if(race.improvesVelocity(boatVMG, newVMG, boat.calculateBearingToNextMarker())){ boat.setVMG(newVMG); }*/ + Bearing newBearing = Bearing.fromDegrees(360d - boat.getBearing().degrees()); + boat.setBearing(newBearing); } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java index 5d11a27b..a28cfa57 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -10,15 +10,16 @@ import shared.model.Bearing; */ public class VMGCommand implements CommandFactory { + private MockBoat boat; + + public VMGCommand(final MockBoat boat){ + this.boat = boat; + } + + //The refactoring of MockRace will require changes to be made @Override - public void runCommand(MockBoat boat, MockRace race) { - /*VMG newVMG = boat.getPolars().calculateVMG( - race.getWindDirection(), - race.getWindSpeed(), - boat.calculateBearingToNextMarker(), - Bearing.fromDegrees(0d), - Bearing.fromDegrees(359.99999d)); - boat.setVMG(newVMG);*/ + public void execute() { + //MOCKBOAT SHOULD HAVE PARAMETER TO TOGGLE AUTO-VMG ON AND OFF } } From be8b0e672dfb2c77be2283e66ef89687d694bb02 Mon Sep 17 00:00:00 2001 From: hba56 Date: Fri, 4 Aug 2017 22:01:21 +1200 Subject: [PATCH 07/27] Updated the xml reader to pull in the rounding type of the compound marks and set each mark with that value #story[1101] --- .../java/shared/dataInput/RaceXMLReader.java | 15 ++++++++ .../main/java/shared/enums/RoundingType.java | 34 +++++++++++++++++++ .../main/java/shared/model/CompoundMark.java | 23 +++++++++++++ .../src/main/java/shared/model/Mark.java | 1 - 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 racevisionGame/src/main/java/shared/enums/RoundingType.java diff --git a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java index 7e61b3de..a270056a 100644 --- a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java @@ -5,6 +5,7 @@ import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import shared.enums.RoundingType; import shared.enums.XMLFileType; import shared.exceptions.InvalidRaceDataException; import shared.exceptions.XMLReaderException; @@ -313,6 +314,8 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { return element.getAttribute("Name"); } + private String getCompoundMarkRounding(Element element){return element.getAttribute("Rounding");} + /** * Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements. @@ -331,12 +334,18 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { //Gets the ID number of this corner element. int cornerID = getCompoundMarkID(cornerElement); + //gets the Rounding of this corner element + String cornerRounding = getCompoundMarkRounding(cornerElement); + //Gets the CompoundMark associated with this corner. CompoundMark lastCompoundMark = this.compoundMarkMap.get(cornerID); //The name of the leg is the name of the first compoundMark in the leg. String legName = lastCompoundMark.getName(); + //Sets the rounding type of this compound mark + lastCompoundMark.setRoundingType(RoundingType.valueOf(cornerRounding)); + //For each following corner, create a leg between cornerN and cornerN+1. for(int i = 1; i < corners.getLength(); i++) { @@ -346,9 +355,15 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { //Gets the ID number of this corner element. cornerID = getCompoundMarkID(cornerElement); + //gets the Rounding of this corner element + cornerRounding = getCompoundMarkRounding(cornerElement); + //Gets the CompoundMark associated with this corner. CompoundMark currentCompoundMark = this.compoundMarkMap.get(cornerID); + //Sets the rounding type of this compound mark + currentCompoundMark.setRoundingType(RoundingType.valueOf(cornerRounding)); + //Create a leg from these two adjacent compound marks. Leg leg = new Leg(legName, lastCompoundMark, currentCompoundMark, i - 1); legs.add(leg); diff --git a/racevisionGame/src/main/java/shared/enums/RoundingType.java b/racevisionGame/src/main/java/shared/enums/RoundingType.java new file mode 100644 index 00000000..eaba4e73 --- /dev/null +++ b/racevisionGame/src/main/java/shared/enums/RoundingType.java @@ -0,0 +1,34 @@ +package shared.enums; + +/** + * Enum for the types of rounding that can be done + */ +public enum RoundingType { + /** + * This is means it must be rounded port side + */ + Port, + + /** + * This is means it must be rounded starboard side + */ + Starboard, + + /** + * The boat within the compound mark with the SeqID + * of 1 should be rounded to starboard and the boat + * within the compound mark with the SeqID of 2 should + * be rounded to port. + */ + SP, + + /** + * The boat within the compound mark with the SeqID + * of 1 should be rounded to port and the boat + * within the compound mark with the SeqID of 2 should + * be rounded to starboard. + * + * opposite of SP + */ + PS; +} diff --git a/racevisionGame/src/main/java/shared/model/CompoundMark.java b/racevisionGame/src/main/java/shared/model/CompoundMark.java index b9f45753..38e972dc 100644 --- a/racevisionGame/src/main/java/shared/model/CompoundMark.java +++ b/racevisionGame/src/main/java/shared/model/CompoundMark.java @@ -1,6 +1,8 @@ package shared.model; +import shared.enums.RoundingType; + /** * Represents a compound mark - that is, either one or two individual marks which form a single compound mark. */ @@ -31,6 +33,11 @@ public class CompoundMark { */ private GPSCoordinate averageGPSCoordinate; + /** + * The side that the mark must be rounded on + */ + private RoundingType roundingType; + /** * Constructs a compound mark from a single mark. @@ -141,4 +148,20 @@ public class CompoundMark { return averageCoordinate; } + + /** + * Used to get how this mark should be rounded + * @return rounding type for mark + */ + public RoundingType getRoundingType() { + return roundingType; + } + + /** + * Used to set the type of rounding for this mark + * @param roundingType rounding type to set + */ + public void setRoundingType(RoundingType roundingType) { + this.roundingType = roundingType; + } } diff --git a/racevisionGame/src/main/java/shared/model/Mark.java b/racevisionGame/src/main/java/shared/model/Mark.java index 5781861a..19dc8f26 100644 --- a/racevisionGame/src/main/java/shared/model/Mark.java +++ b/racevisionGame/src/main/java/shared/model/Mark.java @@ -22,7 +22,6 @@ public class Mark { private GPSCoordinate position; - /** * Constructs a mark with a given source ID, name, and position. * @param sourceID The source ID of the mark. From db1efab225d31ca3b6d99d8865fc423d68e603df Mon Sep 17 00:00:00 2001 From: hba56 Date: Fri, 4 Aug 2017 22:17:17 +1200 Subject: [PATCH 08/27] rounding checks are now done by port or starboard side depending on what the compound marks type is #story[1101] --- .../src/main/java/mock/model/MockRace.java | 93 ++++++++++++++----- 1 file changed, 71 insertions(+), 22 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 3c394767..448344a6 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -4,18 +4,17 @@ import javafx.animation.AnimationTimer; import network.Messages.BoatLocation; import network.Messages.BoatStatus; import network.Messages.Enums.BoatStatusEnum; +import network.Messages.Enums.RaceStatusEnum; import network.Messages.LatestMessages; import network.Messages.RaceStatus; import network.Utils.AC35UnitConverter; import shared.dataInput.BoatDataSource; import shared.dataInput.RaceDataSource; -import network.Messages.Enums.RaceStatusEnum; import shared.dataInput.RegattaDataSource; import shared.model.*; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalUnit; import java.util.*; import static java.lang.Math.cos; @@ -771,6 +770,68 @@ public class MockRace extends Race { } + /** + * Checks to be run on boats rounding marks on the port side + * @param boat the boat that is rounding a mark + * @param roundingChecks the checks to run + */ + private void boatRoundingCheckPort(MockBoat boat, List roundingChecks){ + //boats must pass all checks in order to round a mark + switch (boat.getRoundingStatus()) { + case 0://hasn't started rounding + if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && + GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), + roundingChecks.get(0), boat.getCurrentPosition())) { + boat.increaseRoundingStatus(); + } + break; + case 1://has been parallel to the mark + if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && + GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), + roundingChecks.get(1), boat.getCurrentPosition())) { + boat.increaseRoundingStatus(); + } + break; + case 2://has traveled 180 degrees around the mark + //Move boat on to next leg. + boat.resetRoundingStatus(); + Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); + boat.setCurrentLeg(nextLeg); + break; + } + } + + /** + * Checks to be run on boats rounding marks on the starboard side + * @param boat the boat that is rounding a mark + * @param roundingChecks the checks to run + */ + private void boatRoundingCheckStarboard(MockBoat boat, List roundingChecks){ + //boats must pass all checks in order to round a mark + switch (boat.getRoundingStatus()) { + case 0://hasn't started rounding + if (boat.isStarboardSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && + GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), + roundingChecks.get(0), boat.getCurrentPosition())) { + boat.increaseRoundingStatus(); + } + break; + case 1://has been parallel to the mark + if (boat.isStarboardSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && + GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), + roundingChecks.get(1), boat.getCurrentPosition())) { + boat.increaseRoundingStatus(); + } + break; + case 2://has traveled 180 degrees around the mark + //Move boat on to next leg. + boat.resetRoundingStatus(); + Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); + boat.setCurrentLeg(nextLeg); + break; + } + } + /** * Checks if a boat has finished any legs, or has pulled out of race (DNF). * @param boat The boat to check. @@ -798,28 +859,16 @@ public class MockRace extends Race { GPSCoordinate roundCheck2 = GPSCoordinate.calculateNewPosition(startDirectionLinePoint, epsilonNauticalMiles, Azimuth.fromDegrees(bearingOfDirectionLine.degrees())); + List roundingChecks = new ArrayList(Arrays.asList(roundCheck1, roundCheck2)); - //boats must pass all checks in order to round a mark - switch (boat.getRoundingStatus()){ - case 0://hasn't started rounding - if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && - GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), - roundCheck1, boat.getCurrentPosition())){ - boat.increaseRoundingStatus(); - } - break; - case 1://has been parallel to the mark - if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && - GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), - roundCheck2, boat.getCurrentPosition())){ - boat.increaseRoundingStatus(); - } + switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) { + case SP://Not yet implemented so these gates will be rounded port side + case Port: + boatRoundingCheckPort(boat, roundingChecks); break; - case 2://has traveled 180 degrees around the mark - //Move boat on to next leg. - boat.resetRoundingStatus(); - Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); - boat.setCurrentLeg(nextLeg); + case PS://not yet implemented so these gates will be rounded starboard side + case Starboard: + boatRoundingCheckStarboard(boat, roundingChecks); break; } From 0b74acadff0488e2f61359a55567031851c09f3a Mon Sep 17 00:00:00 2001 From: hba56 Date: Fri, 4 Aug 2017 22:19:07 +1200 Subject: [PATCH 09/27] rounding checks are now done by port or starboard side depending on what the compound marks type is #story[1101] --- racevisionGame/src/main/java/mock/model/MockRace.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 448344a6..25997ebd 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -837,7 +837,7 @@ public class MockRace extends Race { * @param boat The boat to check. * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. */ - protected void checkPosition(MockBoat boat, long timeElapsed) {//TODO cater for gates and rounding that aren't port side + protected void checkPosition(MockBoat boat, long timeElapsed) {//TODO cater for gates (SP or PS in the switch) //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. double epsilonNauticalMiles = 250.0 / Constants.NMToMetersConversion; //250 meters. @@ -861,7 +861,7 @@ public class MockRace extends Race { List roundingChecks = new ArrayList(Arrays.asList(roundCheck1, roundCheck2)); - switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) { + switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) {//todo may need to implement SP and PS case SP://Not yet implemented so these gates will be rounded port side case Port: boatRoundingCheckPort(boat, roundingChecks); From 554f8a2a0ffd5525448ede94f4331d546f5ebfb1 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 4 Aug 2017 23:08:07 +1200 Subject: [PATCH 10/27] Added WindCommand and multiple command execution to RaceLogic - Dispatch commands with CompositeCommand - Single WindCommand handles upwind and downwind logic - Changed key bindings as Mac lacks PgUp PgDn - ControllerServer is observable, RaceLogic updates CompositeCommand as observer --- .../src/main/java/mock/model/RaceLogic.java | 26 ++++++++++++++-- .../model/commandFactory/CommandFactory.java | 2 ++ .../commandFactory/CompositeCommand.java | 23 ++++++++++++++ .../model/commandFactory/WindCommand.java | 30 ++++++++++++++++++ .../gameController/ControllerServer.java | 19 ++++++++++-- .../gameController/Keys/KeyFactory.java | 4 +-- .../model/commandFactory/WindCommandTest.java | 31 +++++++++++++++++++ 7 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java create mode 100644 racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java create mode 100644 racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index 9e810761..adc0fe37 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -1,12 +1,20 @@ package mock.model; import javafx.animation.AnimationTimer; +import mock.model.commandFactory.Command; +import mock.model.commandFactory.CommandFactory; +import mock.model.commandFactory.CompositeCommand; +import network.Messages.Enums.BoatActionEnum; import network.Messages.Enums.BoatStatusEnum; import network.Messages.Enums.RaceStatusEnum; import network.Messages.LatestMessages; -import shared.model.Race; +import visualiser.gameController.ControllerServer; -public class RaceLogic implements Runnable { +import java.util.Observable; +import java.util.Observer; +import java.util.Stack; + +public class RaceLogic implements Observer, Runnable { /** * State of current race modified by this object */ @@ -16,6 +24,8 @@ public class RaceLogic implements Runnable { */ private RaceServer server; + private CompositeCommand commands; + /** * Initialises race loop with state and server message queue * @param race state of race to modify @@ -24,6 +34,7 @@ public class RaceLogic implements Runnable { public RaceLogic(MockRace race, LatestMessages messages) { this.race = race; this.server = new RaceServer(race, messages); + this.commands = new CompositeCommand(); } /** @@ -123,7 +134,7 @@ public class RaceLogic implements Runnable { //If it is still racing, update its position. if (boat.getStatus() == BoatStatusEnum.RACING) { - + commands.execute(); race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli()); } @@ -173,4 +184,13 @@ public class RaceLogic implements Runnable { iters++; } }; + + @Override + public void update(Observable o, Object arg) { + ControllerServer server = (ControllerServer)o; + BoatActionEnum action = server.getAction(); + MockBoat boat = race.getBoats().get(0); + + commands.addCommand(CommandFactory.createCommand(race, boat, action)); + } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java index 6e87f11b..fba06cb5 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -19,6 +19,8 @@ public class CommandFactory { switch(action) { case AUTO_PILOT: return new VMGCommand(race, boat); case TACK_GYBE: return new TackGybeCommand(race, boat); + case UPWIND: return new WindCommand(race, boat, true); + case DOWNWIND: return new WindCommand(race, boat, false); default: return null; // TODO - please please have discussion over what to default to } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java new file mode 100644 index 00000000..12690d29 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java @@ -0,0 +1,23 @@ +package mock.model.commandFactory; + +import java.util.Stack; + +/** + * Wraps multiple commands into a composite to execute queued commands during a frame. + */ +public class CompositeCommand implements Command { + private Stack commands; + + public CompositeCommand() { + this.commands = new Stack<>(); + } + + public void addCommand(Command command) { + commands.push(command); + } + + @Override + public void execute() { + while(!commands.isEmpty()) commands.pop().execute(); + } +} diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java new file mode 100644 index 00000000..b254f087 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java @@ -0,0 +1,30 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; +import mock.model.MockRace; +import shared.model.Bearing; + +/** + * Created by connortaylorbrown on 4/08/17. + */ +public class WindCommand implements Command { + private MockRace race; + private MockBoat boat; + private int direction; + + public WindCommand(MockRace race, MockBoat boat, boolean upwind) { + this.race = race; + this.boat = boat; + this.direction = upwind? 1 : -1; + } + + @Override + public void execute() { + double wind = race.getWindDirection().degrees(); + double heading = boat.getBearing().degrees(); + + double offset = 3; + if(wind - heading < 0) offset *= -1 * direction; + boat.setBearing(Bearing.fromDegrees(heading + offset)); + } +} diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index fb6a257b..a2f8c80e 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -1,5 +1,7 @@ package visualiser.gameController; +import mock.model.commandFactory.Command; +import mock.model.commandFactory.CommandFactory; import network.BinaryMessageDecoder; import network.MessageDecoders.BoatActionDecoder; import network.Messages.Enums.BoatActionEnum; @@ -9,11 +11,12 @@ import visualiser.gameController.Keys.KeyFactory; import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; +import java.util.Observable; /** * Service for dispatching key press data to race from client */ -public class ControllerServer implements Runnable { +public class ControllerServer extends Observable implements Runnable { /** * Socket to client */ @@ -22,6 +25,10 @@ public class ControllerServer implements Runnable { * Wrapper for input from client */ private DataInputStream inputStream; + /** + * Last received boat action + */ + private BoatActionEnum action; /** * Initialise server-side controller with live client socket @@ -36,6 +43,10 @@ public class ControllerServer implements Runnable { } } + public BoatActionEnum getAction() { + return action; + } + /** * Wait for controller key input from client and loop. */ @@ -48,8 +59,10 @@ public class ControllerServer implements Runnable { inputStream.read(message); BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message); BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody()); - BoatActionEnum decodedMessage = boatActionDecoder.getBoatAction(); - System.out.println("Received key: " + decodedMessage); + action = boatActionDecoder.getBoatAction(); + + this.notifyObservers(); + this.setChanged(); } } catch (IOException e) { e.printStackTrace(); diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java index ef1368f0..be95abd3 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java @@ -27,8 +27,8 @@ public class KeyFactory { keyState.put("SPACE", new VMGKey("VMG")); keyState.put("SHIFT", new SailsToggleKey("Toggle Sails")); keyState.put("ENTER", new TackGybeKey("Tack/Gybe")); - keyState.put("PAGE_UP", new UpWindKey("Upwind")); - keyState.put("PAGE_DOWN", new DownWindKey("Downwind")); + keyState.put("UP", new UpWindKey("Upwind")); + keyState.put("DOWN", new DownWindKey("Downwind")); } /** diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java new file mode 100644 index 00000000..c3d0df04 --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java @@ -0,0 +1,31 @@ +package mock.model.commandFactory; + +import mock.model.MockRace; +import network.Messages.Enums.BoatActionEnum; +import org.junit.Before; +import org.junit.Test; +import shared.model.Boat; +import shared.model.Race; +import visualiser.model.VisualiserRace; + +import static org.testng.Assert.*; + +/** + * Created by connortaylorbrown on 4/08/17. + */ +public class WindCommandTest { + private Race race; + private Boat boat; + private Command upwind; + private Command downwind; + + @Before + public void setUp() { + boat = new Boat(0, "Bob", "NZ"); + } + + @Test + public void upwindCommandDecreasesAngle() { + + } +} \ No newline at end of file From 074e2e590bcbfa968dfed5a306967fb2b4d5d3ea Mon Sep 17 00:00:00 2001 From: zwu18 Date: Sun, 6 Aug 2017 14:48:46 +1200 Subject: [PATCH 11/27] VMGCommand now toggles autoVMG on and off. Linked up observer and observable so the current boat can now be controlled. #Story[1097] --- .../src/main/java/mock/app/ConnectionAcceptor.java | 13 ++++++++++++- racevisionGame/src/main/java/mock/app/Event.java | 5 +++++ .../src/main/java/mock/model/MockBoat.java | 4 ++++ .../mock/model/commandFactory/TackGybeCommand.java | 2 ++ .../java/mock/model/commandFactory/VMGCommand.java | 7 +++++++ .../visualiser/gameController/ControllerServer.java | 9 ++++++++- 6 files changed, 38 insertions(+), 2 deletions(-) diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index 97a974a9..37ed83ed 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -1,5 +1,6 @@ package mock.app; +import mock.model.RaceLogic; import network.Messages.Enums.XMLMessageType; import network.Messages.LatestMessages; import network.Messages.XMLMessage; @@ -43,6 +44,10 @@ public class ConnectionAcceptor implements Runnable { private short boatXMLSequenceNumber; //regatta xml sequence number private short regattaXMLSequenceNumber; + //controller server + private ControllerServer controllerServer; + // + private RaceLogic rl = null; /** * Connection Acceptor Constructor @@ -65,6 +70,11 @@ public class ConnectionAcceptor implements Runnable { return serverPort; } + + public void setRace(RaceLogic rl){ + this.rl = rl; + } + /** * Run the Acceptor */ @@ -76,9 +86,10 @@ public class ConnectionAcceptor implements Runnable { Socket mockSocket = serverSocket.accept(); DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser); - ControllerServer controllerServer = new ControllerServer(mockSocket); + this.controllerServer = new ControllerServer(mockSocket, rl); new Thread(mockOutput).start(); new Thread(controllerServer).start(); + System.out.println("I'm in connectionAcceptor"); mockOutputList.add(mockOutput); System.out.println(String.format("%d number of Visualisers Connected.", mockOutputList.size())); } catch (IOException e) { diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index b4b0586c..15113159 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -82,6 +82,7 @@ public class Event { * @throws InvalidRegattaDataException Thrown if the regatta xml file cannot be parsed. */ public void start() throws InvalidRaceDataException, XMLReaderException, InvalidBoatDataException, InvalidRegattaDataException { + new Thread(mockOutput).start(); sendXMLs(); @@ -94,7 +95,11 @@ public class Event { //Create and start race. RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages); + mockOutput.setRace(newRace); + new Thread(newRace).start(); + + System.out.println("I'm in event"); } /** diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index 104fa264..2ed15b6c 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -203,4 +203,8 @@ public class MockBoat extends Boat { public void setAutoVMG(boolean autoVMG) { this.autoVMG = autoVMG; } + + public boolean getAutoVMG(){ + return autoVMG; + } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index 150a1da8..11acfa0a 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -2,6 +2,7 @@ package mock.model.commandFactory; import mock.model.MockBoat; import mock.model.MockRace; +import shared.model.Bearing; /** * Created by David on 2/08/2017. @@ -28,5 +29,6 @@ public class TackGybeCommand implements Command { if(race.improvesVelocity(boatVMG, newVMG, boat.calculateBearingToNextMarker())){ boat.setVMG(newVMG); }*/ + this.boat.setBearing(Bearing.fromDegrees(360 - boat.getBearing().degrees())); } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java index 64cc6a9f..9cd558a5 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -25,5 +25,12 @@ public class VMGCommand implements Command { Bearing.fromDegrees(0d), Bearing.fromDegrees(359.99999d)); boat.setVMG(newVMG);*/ + if (boat.getAutoVMG()){ + boat.setAutoVMG(false); + System.out.println("Auto VMG off!"); + } else { + boat.setAutoVMG(true); + System.out.println("Auto VMG on!"); + } } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index a2f8c80e..af2efec9 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -1,5 +1,6 @@ package visualiser.gameController; +import mock.model.RaceLogic; import mock.model.commandFactory.Command; import mock.model.commandFactory.CommandFactory; import network.BinaryMessageDecoder; @@ -29,13 +30,19 @@ public class ControllerServer extends Observable implements Runnable { * Last received boat action */ private BoatActionEnum action; + /** + * + */ + private RaceLogic rc; /** * Initialise server-side controller with live client socket * @param socket to client */ - public ControllerServer(Socket socket) { + public ControllerServer(Socket socket, RaceLogic rc) { this.socket = socket; + this.rc = rc; + this.addObserver(rc); try { this.inputStream = new DataInputStream(this.socket.getInputStream()); } catch (IOException e) { From 18f14c7542e258b466b8b624c5dda72f3293358e Mon Sep 17 00:00:00 2001 From: zwu18 Date: Sun, 6 Aug 2017 16:12:53 +1200 Subject: [PATCH 12/27] VMGCommand now toggles autoVMG on and off. Linked up observer and observable so the current boat can now be controlled. Fixed autoVMG not working. #Story[1097] --- .../src/main/java/mock/model/MockRace.java | 2 +- .../model/commandFactory/TackGybeCommand.java | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 0ae5cfcc..7cb91ebc 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -350,7 +350,7 @@ public class MockRace extends Race { } private void newOptimalVMG(MockBoat boat) { - long tackPeriod = 15000; + long tackPeriod = 1000; if (boat.getTimeSinceTackChange() > tackPeriod) { //Calculate the new VMG. diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index 11acfa0a..ff04c54e 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -2,6 +2,7 @@ package mock.model.commandFactory; import mock.model.MockBoat; import mock.model.MockRace; +import mock.model.VMG; import shared.model.Bearing; /** @@ -19,16 +20,14 @@ public class TackGybeCommand implements Command { //The refactoring of MockRace will require changes to be made @Override public void execute() { - /*VMG newVMG = boat.getPolars().calculateVMG( - race.getWindDirection(), - race.getWindSpeed(), - boat.calculateBearingToNextMarker(), - Bearing.fromDegrees(0d), - Bearing.fromDegrees(359.99999d)); - VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing()); - if(race.improvesVelocity(boatVMG, newVMG, boat.calculateBearingToNextMarker())){ - boat.setVMG(newVMG); + /*if(boat.getBearing().degrees()>180){ + boat.setBearing(Bearing.fromDegrees(360 - race.getWindDirection().degrees())); + } else { + boat.setBearing(Bearing.fromDegrees(race.getWindDirection().degrees())); }*/ - this.boat.setBearing(Bearing.fromDegrees(360 - boat.getBearing().degrees())); + System.out.println(race.getWindDirection().degrees()); + double angle = Math.max(race.getWindDirection().degrees(), boat.getBearing().degrees()) - Math.min(race.getWindDirection().degrees(), boat.getBearing().degrees()); + boat.setBearing(Bearing.fromDegrees(angle)); } } + From a38898982764d19815b981b0e1879d50f05fb706 Mon Sep 17 00:00:00 2001 From: zwu18 Date: Sun, 6 Aug 2017 16:38:44 +1200 Subject: [PATCH 13/27] Reworked TackGybeCommand. Current boat in race will now tack and gybe when control is pressed. #Story[1097] --- .../model/commandFactory/TackGybeCommand.java | 22 ++++++++++++++++--- .../mock/model/commandFactory/VMGCommand.java | 7 ------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index ff04c54e..f964a17e 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -25,9 +25,25 @@ public class TackGybeCommand implements Command { } else { boat.setBearing(Bearing.fromDegrees(race.getWindDirection().degrees())); }*/ - System.out.println(race.getWindDirection().degrees()); - double angle = Math.max(race.getWindDirection().degrees(), boat.getBearing().degrees()) - Math.min(race.getWindDirection().degrees(), boat.getBearing().degrees()); - boat.setBearing(Bearing.fromDegrees(angle)); + /*double angle = Math.max(race.getWindDirection().degrees(), boat.getBearing().degrees()) - Math.min(race.getWindDirection().degrees(), boat.getBearing().degrees()); + boat.setBearing(Bearing.fromDegrees(angle));*/ + double boatAngle = boat.getBearing().degrees(); + double windAngle =race.getWindDirection().degrees(); + double differenceAngle = calcDistance(boatAngle, windAngle); + double angleA = windAngle + differenceAngle; + double angleB = windAngle - differenceAngle; + if(angleA % 360 == boatAngle){ + boat.setBearing(Bearing.fromDegrees(angleB)); + } else { + boat.setBearing(Bearing.fromDegrees(angleA)); + } + } + + private double calcDistance(double degreeA, double degreeB){ + double phi = Math.abs(degreeB - degreeA) % 360; + double distance = phi > 180 ? 360 - phi : phi; + return distance; } + } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java index 9cd558a5..d6e3d988 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -18,13 +18,6 @@ public class VMGCommand implements Command { //The refactoring of MockRace will require changes to be made @Override public void execute() { - /*VMG newVMG = boat.getPolars().calculateVMG( - race.getWindDirection(), - race.getWindSpeed(), - boat.calculateBearingToNextMarker(), - Bearing.fromDegrees(0d), - Bearing.fromDegrees(359.99999d)); - boat.setVMG(newVMG);*/ if (boat.getAutoVMG()){ boat.setAutoVMG(false); System.out.println("Auto VMG off!"); From ddaa2623ca3eb2a11ae4d2709b231cbf2e7cd710 Mon Sep 17 00:00:00 2001 From: zwu18 Date: Mon, 7 Aug 2017 02:56:24 +1200 Subject: [PATCH 14/27] Added tests for TackGybeCommand class. #Story[1097] --- .../model/commandFactory/TackGybeCommand.java | 2 +- .../commandFactory/TackGybeCommandTest.java | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 racevisionGame/src/test/java/mock/model/commandFactory/TackGybeCommandTest.java diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index f964a17e..447ab2fb 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -39,7 +39,7 @@ public class TackGybeCommand implements Command { } } - private double calcDistance(double degreeA, double degreeB){ + public double calcDistance(double degreeA, double degreeB){ double phi = Math.abs(degreeB - degreeA) % 360; double distance = phi > 180 ? 360 - phi : phi; return distance; diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/TackGybeCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/TackGybeCommandTest.java new file mode 100644 index 00000000..7ac71956 --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/commandFactory/TackGybeCommandTest.java @@ -0,0 +1,42 @@ +package mock.model.commandFactory; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Created by David on 7/08/2017. + */ +public class TackGybeCommandTest { + + private double degreeA; + private double degreeB; + private double degreeC; + private double degreeD; + TackGybeCommand tgc; + + //Run before tests + @Before + public void setUp(){ + degreeA = 150.0; + degreeB = 300.0; + degreeC = 10.0; + degreeD = 350.0; + tgc = new TackGybeCommand(null, null); + } + + //Test when degree difference is <180 + @Test + public void angleDistanceCalculationLow(){ + double result = tgc.calcDistance(degreeA, degreeB); + assertEquals(150.0, result, 0); + } + + //Test when degree difference is >180 + @Test + public void angleDistanceCalculationHigh(){ + double result = tgc.calcDistance(degreeC, degreeD); + assertEquals(20.0, result, 0); + } +} From 0bf2c6106b21833485d9a2ef4521add1754f18b5 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Mon, 7 Aug 2017 12:03:34 +1200 Subject: [PATCH 15/27] Boat updating speed after key press has been fixed. #fix #story[1094] --- racevisionGame/src/main/java/mock/model/MockRace.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 7cb91ebc..3ac8418e 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -374,8 +374,8 @@ public class MockRace extends Race { this.getWindDirection(), this.getWindSpeed(), boat.getBearing(), - boat.getBearing(), - boat.getBearing()); + Bearing.fromDegrees(boat.getBearing().degrees() - 1), + Bearing.fromDegrees(boat.getBearing().degrees() + 1)); if (vmg.getSpeed() > 0) { boat.setCurrentSpeed(vmg.getSpeed()); } From 950a801d1609b09e7ef2465be97f239da810daa7 Mon Sep 17 00:00:00 2001 From: hba56 Date: Mon, 7 Aug 2017 12:16:37 +1200 Subject: [PATCH 16/27] rounding fix for the xml #story[1087] --- .idea/codeStyleSettings.xml | 6 ++++++ .../src/main/java/mock/model/MockBoat.java | 2 +- .../src/main/java/mock/model/MockRace.java | 3 +++ .../main/java/shared/dataInput/RaceXMLReader.java | 3 ++- .../src/main/java/shared/enums/RoundingType.java | 15 +++++++++++++++ .../src/main/resources/mock/mockXML/raceTest.xml | 12 ++++++------ 6 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 .idea/codeStyleSettings.xml diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 00000000..5352bdf8 --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index 2874aa30..795e2675 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -28,7 +28,7 @@ public class MockBoat extends Boat { * 1: passed only first check * 2: passed first and second check */ - private Integer roundingStatus; + private Integer roundingStatus = 0; diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 25997ebd..a3be5381 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -779,6 +779,7 @@ public class MockRace extends Race { //boats must pass all checks in order to round a mark switch (boat.getRoundingStatus()) { case 0://hasn't started rounding + System.out.println("round 0"); if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), roundingChecks.get(0), boat.getCurrentPosition())) { @@ -786,6 +787,7 @@ public class MockRace extends Race { } break; case 1://has been parallel to the mark + System.out.println("round 1"); if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), roundingChecks.get(1), boat.getCurrentPosition())) { @@ -793,6 +795,7 @@ public class MockRace extends Race { } break; case 2://has traveled 180 degrees around the mark + System.out.println("round 2"); //Move boat on to next leg. boat.resetRoundingStatus(); Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); diff --git a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java index a270056a..135cd988 100644 --- a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java @@ -344,7 +344,8 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { String legName = lastCompoundMark.getName(); //Sets the rounding type of this compound mark - lastCompoundMark.setRoundingType(RoundingType.valueOf(cornerRounding)); + + lastCompoundMark.setRoundingType(RoundingType.getValueOf(cornerRounding)); //For each following corner, create a leg between cornerN and cornerN+1. for(int i = 1; i < corners.getLength(); i++) { diff --git a/racevisionGame/src/main/java/shared/enums/RoundingType.java b/racevisionGame/src/main/java/shared/enums/RoundingType.java index eaba4e73..8f8e719a 100644 --- a/racevisionGame/src/main/java/shared/enums/RoundingType.java +++ b/racevisionGame/src/main/java/shared/enums/RoundingType.java @@ -31,4 +31,19 @@ public enum RoundingType { * opposite of SP */ PS; + + public static RoundingType getValueOf(String value) { + switch (value) { + case "Port": + return RoundingType.Port; + case "Starboard": + return RoundingType.Starboard; + case "SP": + return RoundingType.Port; + case "PS": + return RoundingType.Starboard; + default: + return null; + } + } } diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml b/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml index 83e36f85..e068e9b8 100644 --- a/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml +++ b/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml @@ -8,12 +8,12 @@ - - - - - - + + + + + + From 4195d41814021cd1ba6f7e27e82d104bcd196f30 Mon Sep 17 00:00:00 2001 From: hba56 Date: Mon, 7 Aug 2017 12:28:44 +1200 Subject: [PATCH 17/27] removed outdated comments #story[1087] --- racevisionGame/src/main/java/mock/model/MockBoat.java | 2 ++ racevisionGame/src/main/java/mock/model/MockRace.java | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index 795e2675..214d9c10 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -71,6 +71,8 @@ public class MockBoat extends Boat { //Get the start and end points. GPSCoordinate currentPosition = this.getCurrentPosition(); GPSCoordinate nextMarkerPosition = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate(); +// GPSCoordinate nextMarkerPosition = this.getCurrentLeg().getEndCompoundMark().getMark1Position(); +// todo:may need to change this so that boats are send to corners of the gates rather than the middle //Calculate bearing. Bearing bearing = GPSCoordinate.calculateBearing(currentPosition, nextMarkerPosition); diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index a3be5381..c534219b 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -779,7 +779,7 @@ public class MockRace extends Race { //boats must pass all checks in order to round a mark switch (boat.getRoundingStatus()) { case 0://hasn't started rounding - System.out.println("round 0"); +// System.out.println("round 0"); if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), roundingChecks.get(0), boat.getCurrentPosition())) { @@ -787,7 +787,7 @@ public class MockRace extends Race { } break; case 1://has been parallel to the mark - System.out.println("round 1"); +// System.out.println("round 1"); if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), roundingChecks.get(1), boat.getCurrentPosition())) { @@ -795,7 +795,7 @@ public class MockRace extends Race { } break; case 2://has traveled 180 degrees around the mark - System.out.println("round 2"); +// System.out.println("round 2"); //Move boat on to next leg. boat.resetRoundingStatus(); Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); @@ -840,7 +840,7 @@ public class MockRace extends Race { * @param boat The boat to check. * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. */ - protected void checkPosition(MockBoat boat, long timeElapsed) {//TODO cater for gates (SP or PS in the switch) + protected void checkPosition(MockBoat boat, long timeElapsed) { //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. double epsilonNauticalMiles = 250.0 / Constants.NMToMetersConversion; //250 meters. @@ -864,7 +864,7 @@ public class MockRace extends Race { List roundingChecks = new ArrayList(Arrays.asList(roundCheck1, roundCheck2)); - switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) {//todo may need to implement SP and PS + switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) { case SP://Not yet implemented so these gates will be rounded port side case Port: boatRoundingCheckPort(boat, roundingChecks); From 62752c142a3842a3518ee271b0a7702457284ece Mon Sep 17 00:00:00 2001 From: hba56 Date: Mon, 7 Aug 2017 12:47:55 +1200 Subject: [PATCH 18/27] building blocks for drawing the race line around the course #story[1087] --- racevisionGame/src/main/java/shared/model/Race.java | 7 +++++++ .../java/visualiser/model/ResizableRaceCanvas.java | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java index 415e9f77..8b327fb4 100644 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -323,6 +323,13 @@ public abstract class Race implements Runnable { return lastFps; } + /** + * Returns the legs of this race + * @return list of legs + */ + public List getLegs() { + return legs; + } /** * Increments the FPS counter, and adds timePeriod milliseconds to our FPS reset timer. diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index 7664f854..386468c8 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -1,6 +1,7 @@ package visualiser.model; +import javafx.collections.ObservableList; import javafx.scene.Node; import javafx.scene.image.Image; import javafx.scene.paint.Color; @@ -9,6 +10,7 @@ import javafx.scene.transform.Rotate; import network.Messages.Enums.BoatStatusEnum; import shared.dataInput.RaceDataSource; import shared.model.GPSCoordinate; +import shared.model.Leg; import shared.model.Mark; import shared.model.RaceClock; @@ -516,6 +518,17 @@ public class ResizableRaceCanvas extends ResizableCanvas { } + /** + * draws a transparent line around the course that shows the paths boats must travel + */ + public void drawRaceLine(){ + List legs = this.visualiserRace.getLegs(); + + for (Leg leg: legs) { + //todo calculate and draw line around this leg + } + + } /** * Draws the race boundary image onto the canvas. From e76de1cbf994dd18241864de90c76f5b52e73145 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Mon, 7 Aug 2017 14:13:14 +1200 Subject: [PATCH 19/27] Added test for WindCommand #story[1096] --- .../model/commandFactory/WindCommand.java | 3 +- .../model/commandFactory/WindCommandTest.java | 33 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java index b254f087..d20c0744 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java @@ -12,6 +12,8 @@ public class WindCommand implements Command { private MockBoat boat; private int direction; + private double offset = 3.0; + public WindCommand(MockRace race, MockBoat boat, boolean upwind) { this.race = race; this.boat = boat; @@ -23,7 +25,6 @@ public class WindCommand implements Command { double wind = race.getWindDirection().degrees(); double heading = boat.getBearing().degrees(); - double offset = 3; if(wind - heading < 0) offset *= -1 * direction; boat.setBearing(Bearing.fromDegrees(heading + offset)); } diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java index c3d0df04..4316d78d 100644 --- a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java +++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java @@ -1,31 +1,58 @@ package mock.model.commandFactory; +import mock.model.MockBoat; import mock.model.MockRace; import network.Messages.Enums.BoatActionEnum; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import shared.model.Bearing; import shared.model.Boat; import shared.model.Race; import visualiser.model.VisualiserRace; +import static org.mockito.Mockito.when; import static org.testng.Assert.*; +import static org.mockito.Mockito.mock; /** * Created by connortaylorbrown on 4/08/17. */ public class WindCommandTest { - private Race race; - private Boat boat; + private MockRace race; + private MockBoat boat; private Command upwind; private Command downwind; + private double initial; + + private double offset = 3.0; @Before public void setUp() { - boat = new Boat(0, "Bob", "NZ"); + race = mock(MockRace.class); + boat = new MockBoat(0, "Bob", "NZ", null); + + when(race.getWindDirection()).thenReturn(Bearing.fromDegrees(0.0)); + boat.setBearing(Bearing.fromDegrees(45.0)); + + upwind = CommandFactory.createCommand(race, boat, BoatActionEnum.UPWIND); + downwind = CommandFactory.createCommand(race, boat, BoatActionEnum.DOWNWIND); + + initial = boat.getBearing().degrees(); } + /** + * Ensure the difference between initial and final angle is 3 degrees + */ @Test public void upwindCommandDecreasesAngle() { + upwind.execute(); + assertEquals(initial - boat.getBearing().degrees(), offset, 1e-5); + } + @Test + public void downwindCommandIncreasesAngle() { + downwind.execute(); + assertEquals(boat.getBearing().degrees() - initial, offset, 1e-5); } } \ No newline at end of file From 55798447ab4bce81dd418711af7c9bf33f550a8f Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Mon, 7 Aug 2017 14:25:30 +1200 Subject: [PATCH 20/27] Cherry-picked CommandFactory connection between ControllerServer and RaceLogic. #story[1096] --- .../main/java/mock/app/ConnectionAcceptor.java | 16 +++++++++++----- racevisionGame/src/main/java/mock/app/Event.java | 2 ++ .../gameController/ControllerServer.java | 13 ++++++++----- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index 97a974a9..3584fbab 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -1,20 +1,17 @@ package mock.app; +import mock.model.RaceLogic; import network.Messages.Enums.XMLMessageType; import network.Messages.LatestMessages; import network.Messages.XMLMessage; -import org.mockito.Mock; import visualiser.gameController.ControllerServer; import java.io.DataOutputStream; import java.io.IOException; -import java.lang.reflect.Array; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.ArrayBlockingQueue; /** @@ -43,6 +40,10 @@ public class ConnectionAcceptor implements Runnable { private short boatXMLSequenceNumber; //regatta xml sequence number private short regattaXMLSequenceNumber; + //controller server + private ControllerServer controllerServer; + // + private RaceLogic rl = null; /** * Connection Acceptor Constructor @@ -65,6 +66,11 @@ public class ConnectionAcceptor implements Runnable { return serverPort; } + + public void setRace(RaceLogic rl){ + this.rl = rl; + } + /** * Run the Acceptor */ @@ -76,7 +82,7 @@ public class ConnectionAcceptor implements Runnable { Socket mockSocket = serverSocket.accept(); DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser); - ControllerServer controllerServer = new ControllerServer(mockSocket); + this.controllerServer = new ControllerServer(mockSocket, rl); new Thread(mockOutput).start(); new Thread(controllerServer).start(); mockOutputList.add(mockOutput); diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index b4b0586c..617ad584 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -94,6 +94,8 @@ public class Event { //Create and start race. RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages); + mockOutput.setRace(newRace); + new Thread(newRace).start(); } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index a2f8c80e..c95a0789 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -1,12 +1,9 @@ package visualiser.gameController; -import mock.model.commandFactory.Command; -import mock.model.commandFactory.CommandFactory; +import mock.model.RaceLogic; import network.BinaryMessageDecoder; import network.MessageDecoders.BoatActionDecoder; import network.Messages.Enums.BoatActionEnum; -import visualiser.gameController.Keys.ControlKey; -import visualiser.gameController.Keys.KeyFactory; import java.io.DataInputStream; import java.io.IOException; @@ -29,13 +26,19 @@ public class ControllerServer extends Observable implements Runnable { * Last received boat action */ private BoatActionEnum action; + /** + * + */ + private RaceLogic rc; /** * Initialise server-side controller with live client socket * @param socket to client */ - public ControllerServer(Socket socket) { + public ControllerServer(Socket socket, RaceLogic rc) { this.socket = socket; + this.rc = rc; + this.addObserver(rc); try { this.inputStream = new DataInputStream(this.socket.getInputStream()); } catch (IOException e) { From dad4fa57c69a9b7860cf9381b73ad235dbcb85a0 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Mon, 7 Aug 2017 14:51:32 +1200 Subject: [PATCH 21/27] Fixed WindCommand not updating downwind command when moving upwind. #story[1096] --- .../java/mock/model/commandFactory/WindCommand.java | 10 ++++++---- .../mock/model/commandFactory/WindCommandTest.java | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java index d20c0744..5ba5d5bf 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java @@ -12,12 +12,10 @@ public class WindCommand implements Command { private MockBoat boat; private int direction; - private double offset = 3.0; - public WindCommand(MockRace race, MockBoat boat, boolean upwind) { this.race = race; this.boat = boat; - this.direction = upwind? 1 : -1; + this.direction = upwind? -1 : 1; } @Override @@ -25,7 +23,11 @@ public class WindCommand implements Command { double wind = race.getWindDirection().degrees(); double heading = boat.getBearing().degrees(); - if(wind - heading < 0) offset *= -1 * direction; + double offset = 3.0; + + offset *= direction; + if(wind - heading < 0) offset *= -1; + boat.setBearing(Bearing.fromDegrees(heading + offset)); } } diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java index 4316d78d..e5d147d9 100644 --- a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java +++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java @@ -47,12 +47,12 @@ public class WindCommandTest { @Test public void upwindCommandDecreasesAngle() { upwind.execute(); - assertEquals(initial - boat.getBearing().degrees(), offset, 1e-5); + assertEquals(initial - boat.getBearing().degrees(), -offset, 1e-5); } @Test public void downwindCommandIncreasesAngle() { downwind.execute(); - assertEquals(boat.getBearing().degrees() - initial, offset, 1e-5); + assertEquals(initial - boat.getBearing().degrees(), offset, 1e-5); } } \ No newline at end of file From 2920b6cf235039c344c8158269083e51754afdd9 Mon Sep 17 00:00:00 2001 From: cbt24 Date: Mon, 7 Aug 2017 16:47:25 +1200 Subject: [PATCH 22/27] Removed build-breaking code from MockRace #story[1096] --- .../src/main/java/mock/model/MockRace.java | 83 ------------------- 1 file changed, 83 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 3a6ad64a..fe1a60c1 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -575,13 +575,6 @@ public class MockRace extends Race { boat.setCurrentSpeed(0); boat.setStatus(BoatStatusEnum.FINISHED); - } else if (doNotFinish()) { - //Boat has pulled out of race. - boat.setTimeFinished(timeElapsed); - boat.setCurrentLeg(new Leg("DNF", -1)); - boat.setCurrentSpeed(0); - boat.setStatus(BoatStatusEnum.DNF); - } } @@ -589,82 +582,6 @@ public class MockRace extends Race { } -//old method fo checking if boats passed a mark -// -// /** -// * Checks if a boat has finished any legs, or has pulled out of race (DNF). -// * @param boat The boat to check. -// * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. -// */ -// protected void checkPosition(MockBoat boat, long timeElapsed) { -// -// //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. -// double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. -// -// if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) { -// //Boat has reached its target marker, and has moved on to a new leg. -// -// -// -// //Calculate how much the boat overshot the marker by. -// double overshootMeters = boat.calculateDistanceToNextMarker(); -// -// -// //Move boat on to next leg. -// Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); -// boat.setCurrentLeg(nextLeg); -// -// //Add overshoot distance into the distance travelled for the next leg. -// boat.setDistanceTravelledInLeg(overshootMeters); -// -// //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark. -// boat.setTimeSinceTackChange(999999); -// -// -// //Check if the boat has finished or stopped racing. -// -// if (this.isLastLeg(boat.getCurrentLeg())) { -// //Boat has finished. -// boat.setTimeFinished(timeElapsed); -// boat.setCurrentSpeed(0); -// boat.setStatus(BoatStatusEnum.FINISHED); -// -// } else if (doNotFinish()) { -// //Boat has pulled out of race. -// boat.setTimeFinished(timeElapsed); -// boat.setCurrentLeg(new Leg("DNF", -1)); -// boat.setCurrentSpeed(0); -// boat.setStatus(BoatStatusEnum.DNF); -// -// } -// -// } -// -// } - - - - - /** - * 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; - } - } - - /** - * Decides if a boat should received a DNF status. - * @return True means it should DNF, false means it shouldn't. - */ - protected boolean doNotFinish() { - Random rand = new Random(); - return rand.nextInt(100) < dnfChance; - } - /** From ef3f468b19e9caaa8de5ce17bc541e86d560ba3e Mon Sep 17 00:00:00 2001 From: hba56 Date: Tue, 8 Aug 2017 00:02:07 +1200 Subject: [PATCH 23/27] arrows displayed for each leg #story[1101] --- .../visualiser/model/ResizableRaceCanvas.java | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index 386468c8..10ff60ec 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -1,7 +1,6 @@ package visualiser.model; -import javafx.collections.ObservableList; import javafx.scene.Node; import javafx.scene.image.Image; import javafx.scene.paint.Color; @@ -9,12 +8,8 @@ import javafx.scene.paint.Paint; import javafx.scene.transform.Rotate; import network.Messages.Enums.BoatStatusEnum; import shared.dataInput.RaceDataSource; -import shared.model.GPSCoordinate; -import shared.model.Leg; -import shared.model.Mark; -import shared.model.RaceClock; +import shared.model.*; -import java.time.Duration; import java.util.List; /** @@ -513,6 +508,9 @@ public class ResizableRaceCanvas extends ResizableCanvas { //Marks. drawMarks(); + //Guiding Line + drawRaceLine(); + //Wind arrow. This rotates the wind arrow node. displayWindArrow(this.visualiserRace.getWindDirection().degrees()); @@ -523,11 +521,67 @@ public class ResizableRaceCanvas extends ResizableCanvas { */ public void drawRaceLine(){ List legs = this.visualiserRace.getLegs(); - - for (Leg leg: legs) { - //todo calculate and draw line around this leg + for (int i = 0; i < legs.size() -1; i++) { + drawLineRounding(legs.get(i)); } + } + + private void drawLineRounding(Leg leg){ + //finds the direction of the leg as a bearing + GPSCoordinate startDirectionLinePoint = leg.getStartCompoundMark().getAverageGPSCoordinate(); + GPSCoordinate endDirectionLinePoint = leg.getEndCompoundMark().getAverageGPSCoordinate(); + Bearing bearingOfDirectionLine = GPSCoordinate.calculateBearing(startDirectionLinePoint, endDirectionLinePoint); + + //use the direction line to find a point parallel to it by the mark + GPSCoordinate pointToStartCurve = GPSCoordinate.calculateNewPosition(endDirectionLinePoint, + 150, Azimuth.fromDegrees(bearingOfDirectionLine.degrees()+90)); + + //use the direction line to find a point to curve too + GPSCoordinate pointToEndCurve = GPSCoordinate.calculateNewPosition(endDirectionLinePoint, + 150, Azimuth.fromDegrees(bearingOfDirectionLine.degrees())); + + //use the curve points to find the two control points for the bezier curve + Bearing bearingOfCurveLine = GPSCoordinate.calculateBearing(pointToStartCurve, pointToEndCurve); + GPSCoordinate controlPoint1 = GPSCoordinate.calculateNewPosition(pointToStartCurve, + 150, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+ 50)); + + //change all gps into graph coordinate + GraphCoordinate startPath = this.map.convertGPS(startDirectionLinePoint); + GraphCoordinate curvePoint = this.map.convertGPS(pointToStartCurve); + GraphCoordinate curvePointEnd = this.map.convertGPS(pointToEndCurve); + GraphCoordinate c1 = this.map.convertGPS(controlPoint1); + + gc.setStroke(Color.RED); + gc.setLineWidth(1); + + gc.beginPath(); + gc.moveTo(startPath.getX(), startPath.getY()); + gc.lineTo(curvePoint.getX(), curvePoint.getY()); + gc.bezierCurveTo(c1.getX(), c1.getY(), c1.getX(), c1.getY(), curvePointEnd.getX(), curvePointEnd.getY()); + gc.stroke(); + gc.closePath(); + gc.save(); + + drawArrowHead(controlPoint1, pointToEndCurve); + } + + private void drawArrowHead(GPSCoordinate start, GPSCoordinate end){ + + GraphCoordinate lineStart = this.map.convertGPS(start); + GraphCoordinate lineEnd = this.map.convertGPS(end); + + double arrowAngle = Math.toRadians(45.0); + double arrowLength = 10.0; + double dx = lineStart.getX() - lineEnd.getX(); + double dy = lineStart.getY() - lineEnd.getY(); + double angle = Math.atan2(dy, dx); + double x1 = Math.cos(angle + arrowAngle) * arrowLength + lineEnd.getX(); + double y1 = Math.sin(angle + arrowAngle) * arrowLength + lineEnd.getY(); + double x2 = Math.cos(angle - arrowAngle) * arrowLength + lineEnd.getX(); + double y2 = Math.sin(angle - arrowAngle) * arrowLength + lineEnd.getY(); + gc.strokeLine(lineEnd.getX(), lineEnd.getY(), x1, y1); + gc.strokeLine(lineEnd.getX(), lineEnd.getY(), x2, y2); } /** From 22722286ef40602e231b0fda5d4c11500bbce4c0 Mon Sep 17 00:00:00 2001 From: hba56 Date: Tue, 8 Aug 2017 14:47:11 +1200 Subject: [PATCH 24/27] all paths now join up #story[1087] --- .../visualiser/model/ResizableRaceCanvas.java | 109 ++++++++++++------ 1 file changed, 74 insertions(+), 35 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index 10ff60ec..2263de91 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -521,48 +521,87 @@ public class ResizableRaceCanvas extends ResizableCanvas { */ public void drawRaceLine(){ List legs = this.visualiserRace.getLegs(); + GPSCoordinate legStartPoint = legs.get(0).getStartCompoundMark().getAverageGPSCoordinate(); + GPSCoordinate nextStartPoint; for (int i = 0; i < legs.size() -1; i++) { - drawLineRounding(legs.get(i)); + nextStartPoint = drawLineRounding(legs, i, legStartPoint); + legStartPoint = nextStartPoint; } } - private void drawLineRounding(Leg leg){ - //finds the direction of the leg as a bearing - GPSCoordinate startDirectionLinePoint = leg.getStartCompoundMark().getAverageGPSCoordinate(); - GPSCoordinate endDirectionLinePoint = leg.getEndCompoundMark().getAverageGPSCoordinate(); - Bearing bearingOfDirectionLine = GPSCoordinate.calculateBearing(startDirectionLinePoint, endDirectionLinePoint); - - //use the direction line to find a point parallel to it by the mark - GPSCoordinate pointToStartCurve = GPSCoordinate.calculateNewPosition(endDirectionLinePoint, - 150, Azimuth.fromDegrees(bearingOfDirectionLine.degrees()+90)); - - //use the direction line to find a point to curve too - GPSCoordinate pointToEndCurve = GPSCoordinate.calculateNewPosition(endDirectionLinePoint, - 150, Azimuth.fromDegrees(bearingOfDirectionLine.degrees())); - - //use the curve points to find the two control points for the bezier curve - Bearing bearingOfCurveLine = GPSCoordinate.calculateBearing(pointToStartCurve, pointToEndCurve); - GPSCoordinate controlPoint1 = GPSCoordinate.calculateNewPosition(pointToStartCurve, - 150, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+ 50)); - - //change all gps into graph coordinate - GraphCoordinate startPath = this.map.convertGPS(startDirectionLinePoint); - GraphCoordinate curvePoint = this.map.convertGPS(pointToStartCurve); - GraphCoordinate curvePointEnd = this.map.convertGPS(pointToEndCurve); - GraphCoordinate c1 = this.map.convertGPS(controlPoint1); + /** + * Draws a line around a course that shows where boats need to go. This method + * draws the line leg by leg + * @param legs the legs of a race + * @param index the index of the current leg to use + * @return the end point of the current leg that has been drawn + */ + private GPSCoordinate drawLineRounding(List legs, int index, GPSCoordinate legStartPoint){ + GPSCoordinate startDirectionLinePoint; + GPSCoordinate endDirectionLinePoint; + Bearing bearingOfDirectionLine; + + GPSCoordinate startNextDirectionLinePoint; + GPSCoordinate endNextDirectionLinePoint; + Bearing bearingOfNextDirectionLine; + + //finds the direction of the current leg as a bearing + startDirectionLinePoint = legStartPoint; + endDirectionLinePoint = legs.get(index).getEndCompoundMark().getMark1Position(); + bearingOfDirectionLine = GPSCoordinate.calculateBearing(startDirectionLinePoint, endDirectionLinePoint); + + //finds the direction of the next leg as a bearing + if (index < legs.size() -2){ // not last leg + startNextDirectionLinePoint = legs.get(index + 1).getStartCompoundMark().getMark1Position(); + endNextDirectionLinePoint = legs.get(index + 1).getEndCompoundMark().getMark1Position(); + bearingOfNextDirectionLine = GPSCoordinate.calculateBearing(startNextDirectionLinePoint, endNextDirectionLinePoint); + + //use the direction line to find a point parallel to it by the mark + GPSCoordinate pointToStartCurve = GPSCoordinate.calculateNewPosition(endDirectionLinePoint, + 150, Azimuth.fromDegrees(bearingOfDirectionLine.degrees()+90)); + + //use the direction line to find a point to curve too + GPSCoordinate pointToEndCurve = GPSCoordinate.calculateNewPosition(endDirectionLinePoint, + 150, Azimuth.fromDegrees(bearingOfNextDirectionLine.degrees()+90)); + + //use the curve points to find the two control points for the bezier curve + Bearing bearingOfCurveLine = GPSCoordinate.calculateBearing(pointToStartCurve, pointToEndCurve); + GPSCoordinate controlPoint = GPSCoordinate.calculateNewPosition(pointToStartCurve, + 75, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+ 50)); + + //change all gps into graph coordinate + GraphCoordinate startPath = this.map.convertGPS(startDirectionLinePoint); + GraphCoordinate curvePoint = this.map.convertGPS(pointToStartCurve); + GraphCoordinate curvePointEnd = this.map.convertGPS(pointToEndCurve); + GraphCoordinate c1 = this.map.convertGPS(controlPoint); + + gc.setStroke(Color.RED); + gc.setLineWidth(1); + + gc.beginPath(); + gc.moveTo(startPath.getX(), startPath.getY()); + gc.lineTo(curvePoint.getX(), curvePoint.getY()); + gc.bezierCurveTo(c1.getX(), c1.getY(), c1.getX(), c1.getY(), curvePointEnd.getX(), curvePointEnd.getY()); + gc.stroke(); + gc.closePath(); + gc.save(); - gc.setStroke(Color.RED); - gc.setLineWidth(1); + drawArrowHead(controlPoint, pointToEndCurve); - gc.beginPath(); - gc.moveTo(startPath.getX(), startPath.getY()); - gc.lineTo(curvePoint.getX(), curvePoint.getY()); - gc.bezierCurveTo(c1.getX(), c1.getY(), c1.getX(), c1.getY(), curvePointEnd.getX(), curvePointEnd.getY()); - gc.stroke(); - gc.closePath(); - gc.save(); + return pointToEndCurve; + }else{//last leg so no curve + GraphCoordinate startPath = this.map.convertGPS(legStartPoint); + GraphCoordinate endPath = this.map.convertGPS(legs.get(index).getEndCompoundMark().getAverageGPSCoordinate()); - drawArrowHead(controlPoint1, pointToEndCurve); + gc.beginPath(); + gc.moveTo(startPath.getX(), startPath.getY()); + gc.lineTo(endPath.getX(), endPath.getY()); + gc.stroke(); + gc.closePath(); + gc.save(); + drawArrowHead(legStartPoint, legs.get(index).getEndCompoundMark().getAverageGPSCoordinate()); + return null; + } } private void drawArrowHead(GPSCoordinate start, GPSCoordinate end){ From bb75806781f46618deaf0c3ce4b4469db6efbc51 Mon Sep 17 00:00:00 2001 From: hba56 Date: Tue, 8 Aug 2017 23:23:46 +1200 Subject: [PATCH 25/27] Cleaned up line to be in a shippable shape #story[1101] --- .../visualiser/model/ResizableRaceCanvas.java | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index 2263de91..379f99e5 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -3,8 +3,7 @@ package visualiser.model; import javafx.scene.Node; import javafx.scene.image.Image; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; +import javafx.scene.paint.*; import javafx.scene.transform.Rotate; import network.Messages.Enums.BoatStatusEnum; import shared.dataInput.RaceDataSource; @@ -502,15 +501,15 @@ public class ResizableRaceCanvas extends ResizableCanvas { //Race boundary. drawBoundary(); + //Guiding Line + drawRaceLine(); + //Boats. drawBoats(); //Marks. drawMarks(); - //Guiding Line - drawRaceLine(); - //Wind arrow. This rotates the wind arrow node. displayWindArrow(this.visualiserRace.getWindDirection().degrees()); @@ -558,36 +557,49 @@ public class ResizableRaceCanvas extends ResizableCanvas { //use the direction line to find a point parallel to it by the mark GPSCoordinate pointToStartCurve = GPSCoordinate.calculateNewPosition(endDirectionLinePoint, - 150, Azimuth.fromDegrees(bearingOfDirectionLine.degrees()+90)); + 100, Azimuth.fromDegrees(bearingOfDirectionLine.degrees()+90)); //use the direction line to find a point to curve too GPSCoordinate pointToEndCurve = GPSCoordinate.calculateNewPosition(endDirectionLinePoint, - 150, Azimuth.fromDegrees(bearingOfNextDirectionLine.degrees()+90)); + 100, Azimuth.fromDegrees(bearingOfNextDirectionLine.degrees()+90)); //use the curve points to find the two control points for the bezier curve + GPSCoordinate controlPoint; + GPSCoordinate controlPoint2; Bearing bearingOfCurveLine = GPSCoordinate.calculateBearing(pointToStartCurve, pointToEndCurve); - GPSCoordinate controlPoint = GPSCoordinate.calculateNewPosition(pointToStartCurve, - 75, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+ 50)); + if ((bearingOfDirectionLine.degrees() - bearingOfNextDirectionLine.degrees() +360)%360< 145){ + //small turn + controlPoint = GPSCoordinate.calculateNewPosition(pointToStartCurve, + 50, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+45)); + controlPoint2 = controlPoint; + }else{ + //large turn + controlPoint = GPSCoordinate.calculateNewPosition(pointToStartCurve, + 150, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+90)); + controlPoint2 = GPSCoordinate.calculateNewPosition(pointToEndCurve, + 150, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+90)); + } + //change all gps into graph coordinate GraphCoordinate startPath = this.map.convertGPS(startDirectionLinePoint); GraphCoordinate curvePoint = this.map.convertGPS(pointToStartCurve); GraphCoordinate curvePointEnd = this.map.convertGPS(pointToEndCurve); GraphCoordinate c1 = this.map.convertGPS(controlPoint); + GraphCoordinate c2 = this.map.convertGPS(controlPoint2); - gc.setStroke(Color.RED); - gc.setLineWidth(1); + gc.setLineWidth(2); + gc.setStroke(Color.MEDIUMAQUAMARINE); gc.beginPath(); gc.moveTo(startPath.getX(), startPath.getY()); gc.lineTo(curvePoint.getX(), curvePoint.getY()); - gc.bezierCurveTo(c1.getX(), c1.getY(), c1.getX(), c1.getY(), curvePointEnd.getX(), curvePointEnd.getY()); + drawArrowHead(startDirectionLinePoint, pointToStartCurve); + gc.bezierCurveTo(c1.getX(), c1.getY(), c2.getX(), c2.getY(), curvePointEnd.getX(), curvePointEnd.getY()); gc.stroke(); gc.closePath(); gc.save(); - drawArrowHead(controlPoint, pointToEndCurve); - return pointToEndCurve; }else{//last leg so no curve GraphCoordinate startPath = this.map.convertGPS(legStartPoint); From e021dd328dcaf940393092398230f51a96fcd231 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Tue, 8 Aug 2017 23:56:59 +1200 Subject: [PATCH 26/27] Changed CompositeCommand stack to queue #story[1096] --- .../mock/model/commandFactory/CompositeCommand.java | 11 ++++++----- .../visualiser/gameController/ControllerServer.java | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java index 12690d29..74c5e95b 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java @@ -1,23 +1,24 @@ package mock.model.commandFactory; -import java.util.Stack; +import java.util.ArrayDeque; +import java.util.Queue; /** * Wraps multiple commands into a composite to execute queued commands during a frame. */ public class CompositeCommand implements Command { - private Stack commands; + private Queue commands; public CompositeCommand() { - this.commands = new Stack<>(); + this.commands = new ArrayDeque<>(); } public void addCommand(Command command) { - commands.push(command); + commands.add(command); } @Override public void execute() { - while(!commands.isEmpty()) commands.pop().execute(); + while(!commands.isEmpty()) commands.remove().execute(); } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index c95a0789..66e98d89 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -64,6 +64,7 @@ public class ControllerServer extends Observable implements Runnable { BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody()); action = boatActionDecoder.getBoatAction(); + // Notify observers of most recent action this.notifyObservers(); this.setChanged(); } From e53d72f24b722c79e333e3917e6473925eca84d8 Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 9 Aug 2017 23:25:42 +1200 Subject: [PATCH 27/27] compound marks now can give the mark they have that needs to be rounded this is used to draw the line around the correct mark #story[1101] --- .../main/java/shared/model/CompoundMark.java | 87 +++++++++++++++++++ .../visualiser/model/ResizableRaceCanvas.java | 32 +++++-- 2 files changed, 110 insertions(+), 9 deletions(-) diff --git a/racevisionGame/src/main/java/shared/model/CompoundMark.java b/racevisionGame/src/main/java/shared/model/CompoundMark.java index 38e972dc..05256655 100644 --- a/racevisionGame/src/main/java/shared/model/CompoundMark.java +++ b/racevisionGame/src/main/java/shared/model/CompoundMark.java @@ -164,4 +164,91 @@ public class CompoundMark { public void setRoundingType(RoundingType roundingType) { this.roundingType = roundingType; } + + /** + * Used to find the mark that is to be rounded at a gate when approaching from the south + * will also give the single mark if there is only one + * @param bearing the bearing a boat will approach form + * @return the mark to round + */ + public Mark getMarkForRounding(Bearing bearing){ + Mark westMostMark; + Mark eastMostMark; + Mark northMostMark; + Mark southMostMark; + + //check to see if there are two marks + if (mark2 == null){ + return mark1; + } + + //finds the mark furthest west and east + if(this.getMark1Position().getLatitude() > this.getMark2Position().getLatitude()){ + westMostMark = this.mark1; + eastMostMark = this.mark2; + }else{ + westMostMark = this.mark2; + eastMostMark = this.mark1; + } + + //finds the mark furthest north and south + if(this.getMark1Position().getLongitude() > this.getMark2Position().getLongitude()){ + northMostMark = this.mark1; + southMostMark = this.mark2; + }else{ + northMostMark = this.mark2; + southMostMark = this.mark1; + } + + if (bearing.degrees() > 315 || bearing.degrees() <= 45){ + //north + switch (this.getRoundingType()){ + case SP: + case Port: + return westMostMark; + case PS: + case Starboard: + return eastMostMark; + default:return null; + } + }else if(bearing.degrees() > 45 && bearing.degrees() <= 135){ + //east + switch (this.getRoundingType()){ + case SP: + case Port: + return northMostMark; + case PS: + case Starboard: + return southMostMark; + default:return null; + } + }else if(bearing.degrees() > 135 && bearing.degrees() <= 225){ + //south + switch (this.getRoundingType()){ + case SP: + case Port: + return eastMostMark; + case PS: + case Starboard: + return westMostMark; + default:return null; + } + }else if(bearing.degrees() > 225 && bearing.degrees() <= 315){ + //west + switch (this.getRoundingType()){ + case SP: + case Port: + return southMostMark; + case PS: + case Starboard: + return northMostMark; + default:return null; + } + }else{ + return null; + } + + } + + } diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index 96a8d6f9..333ed54b 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -1,12 +1,13 @@ package visualiser.model; -import javafx.scene.Node; import javafx.scene.image.Image; -import javafx.scene.paint.*; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; import javafx.scene.transform.Rotate; import network.Messages.Enums.BoatStatusEnum; import shared.dataInput.RaceDataSource; +import shared.enums.RoundingType; import shared.model.*; import java.util.List; @@ -510,7 +511,12 @@ public class ResizableRaceCanvas extends ResizableCanvas { //finds the direction of the current leg as a bearing startDirectionLinePoint = legStartPoint; - endDirectionLinePoint = legs.get(index).getEndCompoundMark().getMark1Position(); + GPSCoordinate tempEndDirectionLinePoint = legs.get(index).getEndCompoundMark().getAverageGPSCoordinate(); + + bearingOfDirectionLine = GPSCoordinate.calculateBearing(startDirectionLinePoint, tempEndDirectionLinePoint); + + //after finding the initial bearing pick the mark used for rounding + endDirectionLinePoint = legs.get(index).getEndCompoundMark().getMarkForRounding(bearingOfDirectionLine).getPosition(); bearingOfDirectionLine = GPSCoordinate.calculateBearing(startDirectionLinePoint, endDirectionLinePoint); //finds the direction of the next leg as a bearing @@ -519,13 +525,22 @@ public class ResizableRaceCanvas extends ResizableCanvas { endNextDirectionLinePoint = legs.get(index + 1).getEndCompoundMark().getMark1Position(); bearingOfNextDirectionLine = GPSCoordinate.calculateBearing(startNextDirectionLinePoint, endNextDirectionLinePoint); + double degreesToAdd; + //find which side is need to be used + if (legs.get(index).getEndCompoundMark().getRoundingType() == RoundingType.Port || + legs.get(index).getEndCompoundMark().getRoundingType() == RoundingType.SP){ + degreesToAdd = 90; + }else{ + degreesToAdd = -90; + } + //use the direction line to find a point parallel to it by the mark GPSCoordinate pointToStartCurve = GPSCoordinate.calculateNewPosition(endDirectionLinePoint, - 100, Azimuth.fromDegrees(bearingOfDirectionLine.degrees()+90)); + 100, Azimuth.fromDegrees(bearingOfDirectionLine.degrees()+degreesToAdd)); //use the direction line to find a point to curve too GPSCoordinate pointToEndCurve = GPSCoordinate.calculateNewPosition(endDirectionLinePoint, - 100, Azimuth.fromDegrees(bearingOfNextDirectionLine.degrees()+90)); + 100, Azimuth.fromDegrees(bearingOfNextDirectionLine.degrees()+degreesToAdd)); //use the curve points to find the two control points for the bezier curve GPSCoordinate controlPoint; @@ -534,14 +549,14 @@ public class ResizableRaceCanvas extends ResizableCanvas { if ((bearingOfDirectionLine.degrees() - bearingOfNextDirectionLine.degrees() +360)%360< 145){ //small turn controlPoint = GPSCoordinate.calculateNewPosition(pointToStartCurve, - 50, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+45)); + 50, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+(degreesToAdd/2))); controlPoint2 = controlPoint; }else{ //large turn controlPoint = GPSCoordinate.calculateNewPosition(pointToStartCurve, - 150, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+90)); + 150, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+degreesToAdd)); controlPoint2 = GPSCoordinate.calculateNewPosition(pointToEndCurve, - 150, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+90)); + 150, Azimuth.fromDegrees(bearingOfCurveLine.degrees()+degreesToAdd)); } @@ -618,7 +633,6 @@ public class ResizableRaceCanvas extends ResizableCanvas { * @see TrackPoint */ private void drawTrack(VisualiserBoat boat) { - //Check that track points are enabled. if (this.annoPath) {