From 2672c2b13b2a02ed3a0589bd76edbb7a24153c0e Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Thu, 3 Aug 2017 00:32:17 +1200 Subject: [PATCH 1/6] Separated RaceLogic from MockRace - MockRace satisfies refactor requirement for RaceState - VisualiserRace directly implements Runnable to remove method from MockRace #story[1094] --- .../src/main/java/mock/app/Event.java | 3 +- .../src/main/java/mock/model/MockRace.java | 163 +---------------- .../src/main/java/mock/model/RaceLogic.java | 172 +++++++++++++++++- .../src/main/java/shared/model/Race.java | 4 +- .../java/visualiser/model/VisualiserRace.java | 2 +- 5 files changed, 180 insertions(+), 164 deletions(-) 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/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 08c56a2d..a32787d2 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -1,12 +1,7 @@ package mock.model; -import javafx.animation.AnimationTimer; -import network.Messages.BoatLocation; -import network.Messages.BoatStatus; import network.Messages.Enums.BoatStatusEnum; 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; @@ -26,7 +21,6 @@ import static java.lang.Math.cos; * Is responsible for simulating the race, and sending messages to a MockOutput instance. */ public class MockRace extends Race { - private RaceServer server; /** * An observable list of boats in the race. @@ -82,8 +76,6 @@ public class MockRace extends Race { //Wind. this.setWind(windGenerator.generateBaselineWind()); - - this.server = new RaceServer(this, latestMessages); } /** @@ -115,20 +107,11 @@ public class MockRace extends Race { } - /** - * Runnable for the thread. - */ - public void run() { - initialiseBoats(); - this.countdownTimer.start(); - } - - /** * 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); } @@ -136,7 +119,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(); @@ -167,7 +150,7 @@ public class MockRace extends Race { /** * 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); @@ -179,7 +162,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); @@ -187,144 +170,6 @@ 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. - server.parseBoatLocations(); - - //Parse the marks. - server.parseMarks(); - - // Change wind direction - changeWindDirection(); - - //Parse the race status. - server.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. - 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++; - } - }; - - /** * Initialise the boats in the race. * This sets their starting positions and current legs. diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index b8e904a6..73b38c0d 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -1,4 +1,174 @@ package mock.model; -public class RaceLogic { +import javafx.animation.AnimationTimer; +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.LatestMessages; + +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; + + @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. + long 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/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java index 040e0d19..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 { /** @@ -214,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; } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index 976a4b6e..2969fbb8 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -27,7 +27,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 { /** From 40a3ed1bb00af265d87fd529e76bbad1bf93952b Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Thu, 3 Aug 2017 16:56:52 +1200 Subject: [PATCH 2/6] Boats now store whether autoVMG is on or off, boat speed scales off their bearing relative to the TWA. #story[1094] --- .../src/main/java/mock/model/MockBoat.java | 12 + .../src/main/java/mock/model/MockRace.java | 60 +++-- .../src/main/java/mock/model/RaceLogic.java | 6 +- .../src/main/java/mock/model/RaceState.java | 226 +++++++++++++++++- 4 files changed, 283 insertions(+), 21 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index 3c932126..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; + /** @@ -191,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 5670e989..0ae5cfcc 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -2,11 +2,13 @@ package mock.model; import network.Messages.Enums.BoatStatusEnum; import network.Messages.LatestMessages; +import org.opengis.geometry.primitive.*; import shared.dataInput.BoatDataSource; import shared.dataInput.RaceDataSource; import network.Messages.Enums.RaceStatusEnum; import shared.dataInput.RegattaDataSource; import shared.model.*; +import shared.model.Bearing; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @@ -209,8 +211,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); } } @@ -319,8 +320,14 @@ public class MockRace extends Race { //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); @@ -331,30 +338,47 @@ public class MockRace extends Race { //Move the boat forwards that many meters, and advances its time counters by enough milliseconds. boat.moveForwards(distanceTravelledMeters); + boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + updatePeriodMilliseconds); + if (boat.isAutoVMG()) { + newOptimalVMG(boat); + } + + this.updateEstimatedTime(boat); + } - long tackPeriod = 15000; + } - 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)); + private void newOptimalVMG(MockBoat boat) { + long tackPeriod = 15000; + 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)); - //If the new vmg improves velocity, use it. - if (improvesVelocity(boat, newVMG)) { - boat.setVMG(newVMG); - } + //If the new vmg improves velocity, use it. + if (improvesVelocity(boat, newVMG)) { + boat.setVMG(newVMG); } - - this.updateEstimatedTime(boat); } + } + 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()); + } } /** diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index 73b38c0d..9e810761 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -4,6 +4,7 @@ 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 { /** @@ -100,6 +101,8 @@ public class RaceLogic implements Runnable { */ long lastFrameTime = timeRaceStarted; + long framePeriod = currentTime - lastFrameTime; + @Override public void handle(long arg0) { @@ -109,12 +112,11 @@ public class RaceLogic implements Runnable { //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. - long framePeriod = currentTime - lastFrameTime; + framePeriod = currentTime - lastFrameTime; //For each boat, we update its position, and generate a BoatLocationMessage. for (MockBoat boat : race.getBoats()) { diff --git a/racevisionGame/src/main/java/mock/model/RaceState.java b/racevisionGame/src/main/java/mock/model/RaceState.java index 4b13cbb4..dd72cc97 100644 --- a/racevisionGame/src/main/java/mock/model/RaceState.java +++ b/racevisionGame/src/main/java/mock/model/RaceState.java @@ -1,4 +1,228 @@ 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.*; + +public class RaceState extends Race { + /** + * An observable list of boats in the race. + */ + private List boats; + + private Map boatMap; + + 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); + this.boatMap = this.generateMockBoatMap(this.boats); + } + + /** + * 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; + + } + + /** + * Generates a map of the sourceID to its MockBoat. + * @param boats The list of MockBoats describing boats that are potentially in the race. + * @return A map of sourceID to MockBoats. + */ + private Map generateMockBoatMap(List boats) { + Map boatMap = new HashMap<>(); + + for (MockBoat boat : boats) { + boatMap.put(boat.getSourceID(), boat); + } + + return boatMap; + } + + /** + * 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. + */ + public 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); + + } + + } + + } + + /** + * 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; + } + + /** + * Updates the race time to a specified value, in milliseconds since the unix epoch. + * @param currentTime Milliseconds since unix epoch. + */ + public void updateRaceTime(long currentTime) { + this.raceClock.setUTCTime(currentTime); + } + + public void run() { + initialiseBoats(); + } + + public Wind getWind() { + return wind; + } + + public List getBoats() { + return boats; + } + + public MockBoat getBoatByid(int id) { + return boatMap.get(id); + } } From c83442761ca6502cedc28feb671705fdf4c0a768 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Thu, 3 Aug 2017 17:54:03 +1200 Subject: [PATCH 3/6] Implemented Factory for Command objects and fixed Command Pattern implementation. - Commands now have standard 0-parameter execute method - Commands are created by CommandFactory #story[1094] --- .../mock/model/commandFactory/Command.java | 14 +++++++++++++ .../model/commandFactory/CommandFactory.java | 21 ++++++++++++++++--- .../model/commandFactory/TackGybeCommand.java | 13 ++++++++---- .../mock/model/commandFactory/VMGCommand.java | 13 ++++++++---- 4 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 racevisionGame/src/main/java/mock/model/commandFactory/Command.java 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 index ace2f3be..17834963 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -3,7 +3,22 @@ package mock.model.commandFactory; import mock.model.MockBoat; import mock.model.MockRace; -public interface CommandFactory { - - void runCommand(MockBoat boat, MockRace race); +/** + * 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, int action) { + switch(action) { + case 1: return new VMGCommand(race, boat); + case 4: 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 index ef776536..150a1da8 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -2,17 +2,22 @@ package mock.model.commandFactory; import mock.model.MockBoat; import mock.model.MockRace; -import mock.model.VMG; -import shared.model.Bearing; /** * Created by David on 2/08/2017. */ -public class TackGybeCommand implements CommandFactory { +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 runCommand(MockBoat boat, MockRace race) { + public void execute() { /*VMG newVMG = boat.getPolars().calculateVMG( race.getWindDirection(), race.getWindSpeed(), diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java index 5d11a27b..64cc6a9f 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -2,17 +2,22 @@ package mock.model.commandFactory; import mock.model.MockBoat; import mock.model.MockRace; -import mock.model.VMG; -import shared.model.Bearing; /** * Created by David on 2/08/2017. */ -public class VMGCommand implements CommandFactory { +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 runCommand(MockBoat boat, MockRace race) { + public void execute() { /*VMG newVMG = boat.getPolars().calculateVMG( race.getWindDirection(), race.getWindSpeed(), From 148108a6581b16d346c891f4a1165e20ed832a96 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 3 Aug 2017 21:11:25 +1200 Subject: [PATCH 4/6] CommandFactory uses BoatActionEnum instead of raw ints. #story[1094] --- .../java/mock/model/commandFactory/CommandFactory.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java index 17834963..6e87f11b 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -2,6 +2,7 @@ package mock.model.commandFactory; import mock.model.MockBoat; import mock.model.MockRace; +import network.Messages.Enums.BoatActionEnum; /** * Factory class for Command objects @@ -14,10 +15,10 @@ public class CommandFactory { * @param action number to select command * @return */ - public static Command createCommand(MockRace race, MockBoat boat, int action) { + public static Command createCommand(MockRace race, MockBoat boat, BoatActionEnum action) { switch(action) { - case 1: return new VMGCommand(race, boat); - case 4: return new TackGybeCommand(race, boat); + 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 } } From 851bbb4fde539ed3b3db3bab8bec24c5986657bc Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Thu, 3 Aug 2017 23:03:15 +1200 Subject: [PATCH 5/6] Merged branch "RaceFactory" with master --- .../src/main/java/mock/model/RaceState.java | 228 ------------------ 1 file changed, 228 deletions(-) delete mode 100644 racevisionGame/src/main/java/mock/model/RaceState.java diff --git a/racevisionGame/src/main/java/mock/model/RaceState.java b/racevisionGame/src/main/java/mock/model/RaceState.java deleted file mode 100644 index dd72cc97..00000000 --- a/racevisionGame/src/main/java/mock/model/RaceState.java +++ /dev/null @@ -1,228 +0,0 @@ -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.*; - -public class RaceState extends Race { - /** - * An observable list of boats in the race. - */ - private List boats; - - private Map boatMap; - - 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); - this.boatMap = this.generateMockBoatMap(this.boats); - } - - /** - * 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; - - } - - /** - * Generates a map of the sourceID to its MockBoat. - * @param boats The list of MockBoats describing boats that are potentially in the race. - * @return A map of sourceID to MockBoats. - */ - private Map generateMockBoatMap(List boats) { - Map boatMap = new HashMap<>(); - - for (MockBoat boat : boats) { - boatMap.put(boat.getSourceID(), boat); - } - - return boatMap; - } - - /** - * 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. - */ - public 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); - - } - - } - - } - - /** - * 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; - } - - /** - * Updates the race time to a specified value, in milliseconds since the unix epoch. - * @param currentTime Milliseconds since unix epoch. - */ - public void updateRaceTime(long currentTime) { - this.raceClock.setUTCTime(currentTime); - } - - public void run() { - initialiseBoats(); - } - - public Wind getWind() { - return wind; - } - - public List getBoats() { - return boats; - } - - public MockBoat getBoatByid(int id) { - return boatMap.get(id); - } -} From 554f8a2a0ffd5525448ede94f4331d546f5ebfb1 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 4 Aug 2017 23:08:07 +1200 Subject: [PATCH 6/6] 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