diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index c94a691f..b4b0586c 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -3,6 +3,7 @@ package mock.app; import mock.dataInput.PolarParser; import mock.model.MockRace; import mock.model.Polars; +import mock.model.RaceLogic; import network.Messages.LatestMessages; import shared.dataInput.*; import shared.enums.XMLFileType; @@ -91,7 +92,7 @@ public class Event { RegattaDataSource regattaDataSource = new RegattaXMLReader(this.regattaXML, this.xmlFileType); //Create and start race. - MockRace newRace = new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale); + RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages); new Thread(newRace).start(); } diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index c8c6825b..104fa264 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -22,6 +22,11 @@ public class MockBoat extends Boat { */ private long timeSinceTackChange = 0; + /** + * Stores whether the boat is on autoVMG or not + */ + private boolean autoVMG = true; + /** @@ -66,7 +71,6 @@ public class MockBoat extends Boat { //Calculate bearing. Bearing bearing = GPSCoordinate.calculateBearing(currentPosition, nextMarkerPosition); - return bearing; } @@ -135,20 +139,9 @@ public class MockBoat extends Boat { /** * Moves the boat meters forward in the direction that it is facing * @param meters The number of meters to move forward. - * @param milliseconds The number of milliseconds to advance the boat's timers by. + * */ - public void moveForwards(double meters, long milliseconds) { - - - //Update the boat's time since last tack. - this.setTimeSinceTackChange(this.getTimeSinceTackChange() + milliseconds); - - //Update the time into the current leg. - this.setTimeElapsedInCurrentLeg(this.getTimeElapsedInCurrentLeg() + milliseconds); - - //Update the distance into the current leg. - this.setDistanceTravelledInLeg(this.getDistanceTravelledInLeg() + meters); - + public void moveForwards(double meters) { //Updates the current position of the boat. GPSCoordinate newPosition = GPSCoordinate.calculateNewPosition(this.getCurrentPosition(), meters, Azimuth.fromBearing(this.getBearing())); this.setCurrentPosition(newPosition); @@ -203,4 +196,11 @@ public class MockBoat extends Boat { return distanceTravelledMeters; } + public boolean isAutoVMG() { + return autoVMG; + } + + public void setAutoVMG(boolean autoVMG) { + this.autoVMG = autoVMG; + } } diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 5f177505..d0b40ae3 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -1,17 +1,14 @@ 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 org.opengis.geometry.primitive.*; import shared.dataInput.BoatDataSource; import shared.dataInput.RaceDataSource; import shared.dataInput.RegattaDataSource; import shared.model.*; +import shared.model.Bearing; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @@ -46,22 +43,11 @@ public class MockRace extends Race { */ private int scaleFactor; - - /** - * The percent chance that a boat fails the race, and enters a DNF state, at each checkpoint. - * 0 = 0%, 100 = 100%. - */ - private int dnfChance = 0; - - /** * Object used to generate changes in wind speed/direction. */ private WindGenerator windGenerator; - - - /** * 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). @@ -81,7 +67,6 @@ public class MockRace extends Race { 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), @@ -93,7 +78,6 @@ public class MockRace extends Race { //Wind. this.setWind(windGenerator.generateBaselineWind()); - } /** @@ -125,103 +109,11 @@ public class MockRace extends Race { } - /** - * Runnable for the thread. - */ - public void run() { - initialiseBoats(); - initialiseWindDirection(); - this.countdownTimer.start(); - } - - - /** - * 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) { + public void updateRaceTime(long currentTime) { this.raceClock.setUTCTime(currentTime); } @@ -229,7 +121,7 @@ public class MockRace extends Race { /** * Updates the race status enumeration based on the current time. */ - private void updateRaceStatusEnum() { + public void updateRaceStatusEnum() { //The millisecond duration of the race. Negative means it hasn't started, so we flip sign. long timeToStart = - this.raceClock.getDurationMilli(); @@ -254,55 +146,13 @@ public class MockRace extends Race { } - } - - /** - * 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() { + public void setBoatsStatusToRacing() { for (MockBoat boat : this.boats) { boat.setStatus(BoatStatusEnum.RACING); @@ -314,7 +164,7 @@ public class MockRace extends Race { * 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) { + public void setBoatsTimeNextMark(ZonedDateTime time) { for (MockBoat boat : this.boats) { boat.setEstimatedTimeAtNextMark(time); @@ -322,150 +172,12 @@ public class MockRace extends Race { } - /** - * 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++; - } - }; - - /** * Initialise the boats in the race. * This sets their starting positions and current legs. */ @Override - protected void initialiseBoats() { + public void initialiseBoats() { //Gets the starting positions of the boats. List startingPositions = getSpreadStartingPositions(); @@ -498,8 +210,7 @@ public class MockRace extends Race { 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); - + boat.setTimeSinceTackChange(Long.MAX_VALUE); } } @@ -510,7 +221,7 @@ public class MockRace extends Race { * * @return A list of starting positions. */ - public List getSpreadStartingPositions() { + private List getSpreadStartingPositions() { //The first compound marker of the race - the starting gate. CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark(); @@ -552,33 +263,6 @@ public class MockRace extends Race { } - /** - * Calculates a boat's VMG. - * @param boat The boat to calculate VMG for. - * @param bearingBounds An array containing the lower and upper acceptable bearing bounds to keep the boat in the course. - * @return VMG for the specified boat. - */ - private VMG calculateVMG(MockBoat boat, Bearing[] bearingBounds) { - - //Get the lower and upper acceptable bounds. - Bearing lowerAcceptableBound = bearingBounds[0]; - Bearing upperAcceptableBound = bearingBounds[1]; - - - //Find the VMG inside these bounds. - VMG bestVMG = boat.getPolars().calculateVMG( - this.getWindDirection(), - this.getWindSpeed(), - boat.calculateBearingToNextMarker(), - lowerAcceptableBound, - upperAcceptableBound); - - - 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. @@ -586,7 +270,7 @@ public class MockRace extends Race { * @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) { + 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()); @@ -630,13 +314,19 @@ public class MockRace extends Race { * @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) { + public 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) { + if (!finish && totalElapsedMilliseconds >= updatePeriodMilliseconds) { + if (boat.getCurrentSpeed() == 0) { + newOptimalVMG(boat); + boat.setBearing(boat.calculateBearingToNextMarker()); + } + + setBoatSpeed(boat); //Calculates the distance travelled, in meters, in the current timeslice. double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds); @@ -646,203 +336,50 @@ public class MockRace extends Race { //Move the boat forwards that many meters, and advances its time counters by enough milliseconds. - boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor); - - - //Only get a new VMG if the boat will go outside the course, or X seconds have elapsed. - boolean willStayInsideCourse = this.checkBearingInsideCourse(boat.getBearing(), boat.getCurrentPosition()); - long tackPeriod = 15000; - if (!willStayInsideCourse || (boat.getTimeSinceTackChange() > tackPeriod)) { + boat.moveForwards(distanceTravelledMeters); + boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + updatePeriodMilliseconds); - //Calculate the boat's bearing bounds, to ensure that it doesn't go out of the course. - Bearing[] bearingBounds = this.calculateBearingBounds(boat); - - - //Calculate the new VMG. - VMG newVMG = this.calculateVMG(boat, bearingBounds); - - - //If the new vmg improves velocity, use it. - if (improvesVelocity(boat, newVMG)) { - boat.setVMG(newVMG); - - } else { - //We also need to use the new VMG if our current bearing will take us out of the course. - if (!willStayInsideCourse) { - boat.setVMG(newVMG); - } - } + if (boat.isAutoVMG()) { + newOptimalVMG(boat); } this.updateEstimatedTime(boat); - - - //Check the boats position (update leg and stuff). - this.checkPosition(boat, totalElapsedMilliseconds); - - } - - } - - /** - * Calculates the upper and lower bounds that the boat may have in order to not go outside of the course. - * @param boat The boat to check. - * @return An array of bearings. The first is the lower bound, the second is the upper bound. - */ - private Bearing[] calculateBearingBounds(MockBoat boat) { - - Bearing[] bearings = new Bearing[2]; - - Bearing lowerBearing = Bearing.fromDegrees(0.001); - Bearing upperBearing = Bearing.fromDegrees(359.999); - - - - double lastAngle = -1; - boolean lastAngleWasGood = false; - - //Check all bearings between [0, 360). - for (double angle = 0; angle < 360; angle += 1) { - - //Create bearing from angle. - Bearing bearing = Bearing.fromDegrees(angle); - - //Check that if it is acceptable. - boolean bearingIsGood = this.checkBearingInsideCourse(bearing, boat.getCurrentPosition()); - - - if (lastAngle != -1) { - - if (lastAngleWasGood && !bearingIsGood) { - //We have flipped over from good bearings to bad bearings. So the last good bearing is the upper bearing. - upperBearing = Bearing.fromDegrees(lastAngle); - } - - if (!lastAngleWasGood && bearingIsGood) { - //We have flipped over from bad bearings to good bearings. So the current bearing is the lower bearing. - lowerBearing = Bearing.fromDegrees(angle); - } - - } - - lastAngle = angle; - lastAngleWasGood = bearingIsGood; - } - - - //TODO BUG if it can't find either upper or lower, it returns (0, 359.999). Should return (boatbearing, boatbearing+0.0001) - bearings[0] = lowerBearing; - bearings[1] = upperBearing; - - return bearings; } + private void newOptimalVMG(MockBoat boat) { + long tackPeriod = 15000; - - /** - * Checks if a given bearing, starting at a given position, would put a boat out of the course boundaries. - * @param bearing The bearing to check. - * @param position The position to start from. - * @return True if the bearing would keep the boat in the course, false if it would take it out of the course. - */ - private boolean checkBearingInsideCourse(Bearing bearing, GPSCoordinate position) { - - //Get azimuth from bearing. - Azimuth azimuth = Azimuth.fromBearing(bearing); - - - //Tests to see if a point in front of the boat is out of bounds. - double epsilonMeters = 50d; - GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(position, epsilonMeters, azimuth); - - //If it isn't inside the boundary, calculate new bearing. - if (GPSCoordinate.isInsideBoundary(testCoord, this.shrinkBoundary)) { - return true; - } else { - return false; - } - - } - - - /** - * 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); + if (boat.getTimeSinceTackChange() > tackPeriod) { + //Calculate the new VMG. + VMG newVMG = boat.getPolars().calculateVMG( + this.getWindDirection(), + this.getWindSpeed(), + boat.calculateBearingToNextMarker(), + Bearing.fromDegrees(0d), + Bearing.fromDegrees(359.99999d)); - //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); - + //If the new vmg improves velocity, use it. + if (improvesVelocity(boat, newVMG)) { + boat.setVMG(newVMG); } - } - } - - - - /** - * 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; + private void setBoatSpeed(MockBoat boat) { + VMG vmg = boat.getPolars().calculateVMG( + this.getWindDirection(), + this.getWindSpeed(), + boat.getBearing(), + boat.getBearing(), + boat.getBearing()); + if (vmg.getSpeed() > 0) { + boat.setCurrentSpeed(vmg.getSpeed()); } } - /** - * 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; - } - - - /** * Returns the number of boats that are still active in the race. * They become inactive by either finishing or withdrawing. @@ -873,20 +410,10 @@ public class MockRace extends Race { return boats; } - - /** - * Initialises the wind bearing with the value of the windBaselineBearing. - */ - protected void initialiseWindDirection() { - //Set the starting bearing. - this.setWind(windGenerator.generateBaselineWind()); - } - - /** * Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound]. */ - protected void changeWindDirection() { + public void changeWindDirection() { Wind nextWind = windGenerator.generateNextWind(raceWind.getValue()); @@ -914,4 +441,8 @@ public class MockRace extends Race { } } -} + + public List getCompoundMarks() { + return compoundMarks; + } +} \ No newline at end of file diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java new file mode 100644 index 00000000..9e810761 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -0,0 +1,176 @@ +package mock.model; + +import javafx.animation.AnimationTimer; +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.LatestMessages; +import shared.model.Race; + +public class RaceLogic implements Runnable { + /** + * State of current race modified by this object + */ + private MockRace race; + /** + * High-level interface to AC35 protocol server + */ + private RaceServer server; + + /** + * Initialises race loop with state and server message queue + * @param race state of race to modify + * @param messages to send to server + */ + public RaceLogic(MockRace race, LatestMessages messages) { + this.race = race; + this.server = new RaceServer(race, messages); + } + + /** + * Initialise boats and start countdown timer + */ + @Override + public void run() { + race.initialiseBoats(); + this.countdownTimer.start(); + } + + + /** + * Countdown timer until race starts. + */ + protected AnimationTimer countdownTimer = new AnimationTimer() { + + + long currentTime = System.currentTimeMillis(); + + @Override + public void handle(long arg0) { + + //Update race time. + race.updateRaceTime(currentTime); + + //Update the race status based on the current time. + race.updateRaceStatusEnum(); + + //Provide boat's with an estimated time at next mark until the race starts. + race.setBoatsTimeNextMark(race.getRaceClock().getCurrentTime()); + + //Parse the boat locations. + server.parseBoatLocations(); + + //Parse the marks. + server.parseMarks(); + + // Change wind direction + race.changeWindDirection(); + + //Parse the race status. + server.parseRaceStatus(); + + + if (race.getRaceStatusEnum() == RaceStatusEnum.STARTED) { + race.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; + + long framePeriod = currentTime - lastFrameTime; + + @Override + public void handle(long arg0) { + + //Get the current time. + currentTime = System.currentTimeMillis(); + + //Update race time. + race.updateRaceTime(currentTime); + + //As long as there is at least one boat racing, we still simulate the race. + if (race.getNumberOfActiveBoats() != 0) { + + //Get the time period of this frame. + framePeriod = currentTime - lastFrameTime; + + //For each boat, we update its position, and generate a BoatLocationMessage. + for (MockBoat boat : race.getBoats()) { + + //If it is still racing, update its position. + if (boat.getStatus() == BoatStatusEnum.RACING) { + + race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli()); + + } + + } + + } else { + //Otherwise, the race is over! + raceFinished.start(); + race.setRaceStatusEnum(RaceStatusEnum.FINISHED); + this.stop(); + } + + if (race.getNumberOfActiveBoats() != 0) { + // Change wind direction + race.changeWindDirection(); + + //Parse the boat locations. + server.parseBoatLocations(); + + //Parse the marks. + server.parseMarks(); + + //Parse the race status. + server.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) { + + server.parseRaceStatus(); + + if (iters > 500) { + stop(); + } + iters++; + } + }; +} diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java new file mode 100644 index 00000000..c7e3ab69 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -0,0 +1,153 @@ +package mock.model; + +import network.Messages.BoatLocation; +import network.Messages.BoatStatus; +import network.Messages.LatestMessages; +import network.Messages.RaceStatus; +import network.Utils.AC35UnitConverter; +import shared.model.CompoundMark; +import shared.model.Constants; +import shared.model.Mark; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by connortaylorbrown on 2/08/17. + */ +public class RaceServer { + private MockRace race; + private LatestMessages latestMessages; + + /** + * The sequence number of the latest RaceStatus message sent or received. + */ + private int raceStatusSequenceNumber = 1; + + /** + * The sequence number of the latest BoatLocation message sent or received. + */ + private int boatLocationSequenceNumber = 1; + + + public RaceServer(MockRace race, LatestMessages latestMessages) { + this.race = race; + this.latestMessages = latestMessages; + } + + + /** + * 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, + race.getRaceClock().getCurrentTimeMilli()); + + //Iterates the sequence number. + this.boatLocationSequenceNumber++; + + this.latestMessages.setBoatLocation(boatLocation); + } + + /** + * Parse the compound marker boats through mock output. + */ + public void parseMarks() { + for (CompoundMark compoundMark : race.getCompoundMarks()) { + + //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); + } + } + } + + + /** + * Parse the boats in the race, and send it to mockOutput. + */ + public void parseBoatLocations() { + //Parse each boat. + for (MockBoat boat : race.getBoats()) { + 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(), + race.getRaceClock().getCurrentTimeMilli()); + + //Iterates the sequence number. + this.boatLocationSequenceNumber++; + + this.latestMessages.setBoatLocation(boatLocation); + + } + + + + /** + * Parses the race status, and sends it to mockOutput. + */ + public 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 : race.getBoats()) { + + 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(race.getWindDirection().degrees()); + int windSpeedInt = (int) (race.getWindSpeed() * Constants.KnotsToMMPerSecond); + + //Create race status object, and send it. + RaceStatus raceStatus = new RaceStatus( + System.currentTimeMillis(), + race.getRaceId(), + race.getRaceStatusEnum().getValue(), + race.getRaceClock().getStartingTimeMilli(), + windDirectionInt, + windSpeedInt, + race.getRaceType().getValue(), + boatStatuses); + + this.latestMessages.setRaceStatus(raceStatus); + } +} diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/Command.java b/racevisionGame/src/main/java/mock/model/commandFactory/Command.java new file mode 100644 index 00000000..e0486114 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/Command.java @@ -0,0 +1,14 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; +import mock.model.MockRace; + +/** + * Allows RaceLogic to control MockRace state according to the Command pattern + */ +public interface Command { + /** + * Execute command - standard method name in pattern + */ + void execute(); +} diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java new file mode 100644 index 00000000..6e87f11b --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -0,0 +1,25 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; +import mock.model.MockRace; +import network.Messages.Enums.BoatActionEnum; + +/** + * Factory class for Command objects + */ +public class CommandFactory { + /** + * Generates a command on a race and boat corresponding to the protocol action number. + * @param race to receive command + * @param boat to receive command in race + * @param action number to select command + * @return + */ + public static Command createCommand(MockRace race, MockBoat boat, BoatActionEnum action) { + switch(action) { + case AUTO_PILOT: return new VMGCommand(race, boat); + case TACK_GYBE: return new TackGybeCommand(race, boat); + default: return null; // TODO - please please have discussion over what to default to + } + } +} diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java new file mode 100644 index 00000000..150a1da8 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -0,0 +1,32 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; +import mock.model.MockRace; + +/** + * Created by David on 2/08/2017. + */ +public class TackGybeCommand implements Command { + private MockRace race; + private MockBoat boat; + + public TackGybeCommand(MockRace race, MockBoat boat) { + this.race = race; + this.boat = boat; + } + + //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); + }*/ + } +} diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java new file mode 100644 index 00000000..64cc6a9f --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -0,0 +1,29 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; +import mock.model.MockRace; + +/** + * Created by David on 2/08/2017. + */ +public class VMGCommand implements Command { + private MockRace race; + private MockBoat boat; + + public VMGCommand(MockRace race, MockBoat boat) { + this.race = race; + this.boat = boat; + } + + //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);*/ + } +} diff --git a/racevisionGame/src/main/java/shared/model/Constants.java b/racevisionGame/src/main/java/shared/model/Constants.java index 51666bf2..a6fe3844 100644 --- a/racevisionGame/src/main/java/shared/model/Constants.java +++ b/racevisionGame/src/main/java/shared/model/Constants.java @@ -38,7 +38,7 @@ public class Constants { /** * The race pre-start time, in milliseconds. 3 minutes. */ - public static final long RacePreStartTime = 3 * 60 * 1000; + public static final long RacePreStartTime = 1 * 10 * 1000; /** diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java index aec57882..f9fc984e 100644 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -19,7 +19,7 @@ import java.util.List; * This is a base class inherited by {@link mock.model.MockRace} and {@link visualiser.model.VisualiserRace}. * Has a course, state, wind, boundaries, etc.... Boats are added by inheriting classes (see {@link Boat}, {@link mock.model.MockBoat}, {@link visualiser.model.VisualiserBoat}. */ -public abstract class Race implements Runnable { +public abstract class Race { /** @@ -43,18 +43,6 @@ public abstract class Race implements Runnable { */ protected LatestMessages latestMessages; - /** - * The sequence number of the latest BoatLocation message sent or received. - */ - protected int boatLocationSequenceNumber = 1; - - /** - * The sequence number of the latest RaceStatus message sent or received. - */ - protected int raceStatusSequenceNumber = 1; - - - /** * A list of compound marks in the race. */ @@ -226,7 +214,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; } @@ -364,4 +352,8 @@ public abstract class Race implements Runnable { this.lastFpsResetTime = 0; } } + + public int getRaceId() { + return raceId; + } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java index f60f4b9d..2e7b6790 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java @@ -46,11 +46,11 @@ public class ControllerClient { * @throws IOException if socket write fails */ public void sendKey(ControlKey key) throws IOException { - int protocolCode = key.getProtocolCode(); - if(protocolCode > -1) { + BoatActionEnum protocolCode = key.getProtocolCode(); + if(protocolCode != BoatActionEnum.NOT_A_STATUS) { byte[] bytes = new byte[4]; - ByteBuffer.wrap(bytes).putInt(key.getProtocolCode()); + ByteBuffer.wrap(bytes).putInt(protocolCode.getValue()); BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(bytes[3]); BoatAction boatAction = new BoatAction(boatActionEnum); diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java index d5e53cc5..dd489f73 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java @@ -1,19 +1,22 @@ package visualiser.gameController.Keys; +import javafx.scene.input.KeyCode; +import network.Messages.Enums.BoatActionEnum; + /** * Key for the controller, part of the abstract factory KeyFactory */ public abstract class ControlKey { private String name; - protected int protocolCode; + protected BoatActionEnum protocolCode; /** * Constructor for key state with specified protocol code * @param name of action - * @param protocolCode -1 if not sent + * @param protocolCode NOT_A_STATUS if not sent */ - public ControlKey(String name, int protocolCode) { + public ControlKey(String name, BoatActionEnum protocolCode) { this.name = name; this.protocolCode = protocolCode; } @@ -24,10 +27,10 @@ public abstract class ControlKey { */ public ControlKey(String name){ this.name = name; - this.protocolCode = -1; + this.protocolCode = BoatActionEnum.NOT_A_STATUS; } - public int getProtocolCode() { + public BoatActionEnum getProtocolCode() { return protocolCode; } diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java index 6d929ca1..e4b5455a 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java @@ -1,5 +1,7 @@ package visualiser.gameController.Keys; +import network.Messages.Enums.BoatActionEnum; + /** * Key to send downwind packet to server */ @@ -11,7 +13,7 @@ public class DownWindKey extends ControlKey { * */ public DownWindKey(String name) { - super(name, 6); + super(name, BoatActionEnum.DOWNWIND); } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java index d30a7bb8..9d4a0bd8 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java @@ -1,5 +1,6 @@ package visualiser.gameController.Keys; +import network.Messages.Enums.BoatActionEnum; import visualiser.model.ThisBoat; /** @@ -13,7 +14,7 @@ public class SailsToggleKey extends ControlKey { * */ public SailsToggleKey(String name) { - super(name, 2); + super(name, BoatActionEnum.SAILS_IN); } /** @@ -21,16 +22,13 @@ public class SailsToggleKey extends ControlKey { */ @Override public void onAction() { -// 2 = Sails in -// 3 = Sails out - if (ThisBoat.getInstance().isSailsOut()) { - protocolCode = 3; + if(ThisBoat.getInstance().isSailsOut()) { + protocolCode = BoatActionEnum.SAILS_IN; ThisBoat.getInstance().setSailsOut(false); } else { - protocolCode = 2; + protocolCode = BoatActionEnum.SAILS_OUT; ThisBoat.getInstance().setSailsOut(true); } - } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java index cf9a0699..80252e73 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java @@ -1,5 +1,7 @@ package visualiser.gameController.Keys; +import network.Messages.Enums.BoatActionEnum; + /** * key to toggle between tacking and gybing */ @@ -11,7 +13,7 @@ public class TackGybeKey extends ControlKey { * */ public TackGybeKey(String name) { - super(name, 4); + super(name, BoatActionEnum.TACK_GYBE); } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java index 85f7fc4b..333e5f1f 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java @@ -1,5 +1,7 @@ package visualiser.gameController.Keys; +import network.Messages.Enums.BoatActionEnum; + /** * Key to go upwind */ @@ -11,7 +13,7 @@ public class UpWindKey extends ControlKey { * */ public UpWindKey(String name) { - super(name, 5); + super(name, BoatActionEnum.UPWIND); } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java index 384be3aa..c01658bb 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java @@ -1,5 +1,8 @@ package visualiser.gameController.Keys; +import javafx.scene.input.KeyCode; +import network.Messages.Enums.BoatActionEnum; + /** * Key to trigger auto VMG */ @@ -11,7 +14,7 @@ public class VMGKey extends ControlKey{ * @param name name of the key */ public VMGKey(String name) { - super(name, 1); + super(name, BoatActionEnum.AUTO_PILOT); } @Override diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index 35242c8d..78d344e6 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -416,8 +416,9 @@ public class ResizableRaceCanvas extends ResizableCanvas { this.map.setWidth((int) getWidth()); this.map.setHeight((int) getHeight()); - redrawBoundaryImage(); + //Draw the race. drawRace(); + } /** @@ -428,14 +429,14 @@ public class ResizableRaceCanvas extends ResizableCanvas { } /** - * Draws the race boundary, and saves the image to {@link #background}. - * You should call {@link #clear()} before calling this. + * Draws the race boundary. */ - private void redrawBoundaryImage() { + private void drawBoundary() { + //Prepare to draw. gc.setLineWidth(1); gc.setFill(Color.AQUA); - gc.drawImage(new Image(getClass().getClassLoader().getResourceAsStream("images/WaterBackground.png")), 0, 0); + //Calculate the screen coordinates of the boundary. List boundary = this.visualiserRace.getBoundary(); @@ -454,8 +455,6 @@ public class ResizableRaceCanvas extends ResizableCanvas { //Draw the boundary. gc.fillPolygon(xpoints, ypoints, xpoints.length); - //Render boundary to image. - this.background = snapshot(null, null); } /** @@ -471,13 +470,8 @@ public class ResizableRaceCanvas extends ResizableCanvas { drawMarks(); } - /** - * Draws the race boundary image onto the canvas. - * See {@link #background}. - */ - private void drawBoundary() { - gc.drawImage(this.background, 0, 0); - } + + /** * Draws all track points for a given boat. Colour is set by boat, opacity diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index dea82284..342e11dd 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -26,7 +26,7 @@ import java.util.Map; * Has a course, boats, boundaries, etc... * Observes LatestMessages and updates its state based on new messages. */ -public class VisualiserRace extends Race { +public class VisualiserRace extends Race implements Runnable { /** diff --git a/racevisionGame/src/main/resources/css/dayMode.css b/racevisionGame/src/main/resources/css/dayMode.css index b15f242b..d0f62fb7 100644 --- a/racevisionGame/src/main/resources/css/dayMode.css +++ b/racevisionGame/src/main/resources/css/dayMode.css @@ -50,4 +50,6 @@ .scroll-bar > .increment-button:pressed > .increment-arrow, .scroll-bar > .decrement-button:pressed > .decrement-arrow { -fx-background-color: -fx-mark-highlight-color, rgb(255, 255, 255); -} \ No newline at end of file +} + + diff --git a/racevisionGame/src/main/resources/css/nightMode.css b/racevisionGame/src/main/resources/css/nightMode.css index 406cc60b..7fe6a67b 100644 --- a/racevisionGame/src/main/resources/css/nightMode.css +++ b/racevisionGame/src/main/resources/css/nightMode.css @@ -51,4 +51,9 @@ .scroll-bar > .increment-button:pressed > .increment-arrow, .scroll-bar > .decrement-button:pressed > .decrement-arrow { -fx-background-color: -fx-mark-highlight-color, rgb(255, 255, 255); -} \ No newline at end of file +} + + +#arrowImage { + -fx-image: url("/visualiser/images/arrowLight.png"); +} diff --git a/racevisionGame/src/main/resources/visualiser/images/arrowLight.png b/racevisionGame/src/main/resources/visualiser/images/arrowLight.png new file mode 100644 index 00000000..7a80459d Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/arrowLight.png differ diff --git a/racevisionGame/src/test/java/mock/model/WindGeneratorTest.java b/racevisionGame/src/test/java/mock/model/WindGeneratorTest.java new file mode 100644 index 00000000..74ad7421 --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/WindGeneratorTest.java @@ -0,0 +1,111 @@ +package mock.model; + +import org.junit.Before; +import org.junit.Test; +import shared.model.Bearing; +import shared.model.Wind; + +import static org.junit.Assert.*; + + +public class WindGeneratorTest { + + + private WindGenerator windGenerator; + + private Bearing windBaselineBearing; + private Bearing windBearingLowerBound; + private Bearing windBearingUpperBound; + private double windBaselineSpeed; + private double windSpeedLowerBound; + private double windSpeedUpperBound; + + private double speedKnotsEpsilon; + private double bearingDegreeEpsilon; + + + @Before + public void setUp() throws Exception { + + + //Bounds. + this.windBaselineBearing = Bearing.fromDegrees(88.3); + this.windBearingLowerBound = Bearing.fromDegrees(66.5); + this.windBearingUpperBound = Bearing.fromDegrees(248.6); + this.windBaselineSpeed = 13; + this.windSpeedLowerBound = 7; + this.windSpeedUpperBound = 20; + + this.windGenerator = new WindGenerator( + windBaselineBearing, + windBearingLowerBound, + windBearingUpperBound, + windBaselineSpeed, + windSpeedLowerBound, + windSpeedUpperBound ); + + this.bearingDegreeEpsilon = 0.001; + this.speedKnotsEpsilon = 0.001; + } + + + /** + * Tests if the baseline wind generated it accurate. + */ + @Test + public void generateBaselineWindTest() { + + Wind wind = windGenerator.generateBaselineWind(); + + assertEquals(windBaselineSpeed, wind.getWindSpeed(), speedKnotsEpsilon); + assertEquals(windBaselineBearing.degrees(), wind.getWindDirection().degrees(), bearingDegreeEpsilon); + + } + + /** + * Tests if the random wind generated is inside the bounds. + */ + @Test + public void generateRandomWindTest() { + + int randomWindCount = 1000; + + for (int i = 0; i < randomWindCount; i++) { + + Wind wind = windGenerator.generateRandomWind(); + + assertTrue(wind.getWindSpeed() >= windSpeedLowerBound); + assertTrue(wind.getWindSpeed() <= windSpeedUpperBound); + + assertTrue(wind.getWindDirection().degrees() >= windBearingLowerBound.degrees()); + assertTrue(wind.getWindDirection().degrees() <= windBearingUpperBound.degrees()); + + } + + } + + + /** + * Tests if the next wind generated is inside the bounds. + */ + @Test + public void generateNextWindTest() { + + Wind wind = windGenerator.generateBaselineWind(); + + int randomWindCount = 1000; + + for (int i = 0; i < randomWindCount; i++) { + + wind = windGenerator.generateNextWind(wind); + + assertTrue(wind.getWindSpeed() >= windSpeedLowerBound); + assertTrue(wind.getWindSpeed() <= windSpeedUpperBound); + + assertTrue(wind.getWindDirection().degrees() >= windBearingLowerBound.degrees()); + assertTrue(wind.getWindDirection().degrees() <= windBearingUpperBound.degrees()); + + } + + } +}