diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index 85548a45..5fb01ee9 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -1,20 +1,17 @@ package mock.app; +import mock.model.RaceLogic; import network.Messages.Enums.XMLMessageType; import network.Messages.LatestMessages; import network.Messages.XMLMessage; -import org.mockito.Mock; import visualiser.gameController.ControllerServer; import java.io.DataOutputStream; import java.io.IOException; -import java.lang.reflect.Array; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.ArrayBlockingQueue; /** @@ -43,6 +40,10 @@ public class ConnectionAcceptor implements Runnable { private short boatXMLSequenceNumber; //regatta xml sequence number private short regattaXMLSequenceNumber; + //controller server + private ControllerServer controllerServer; + // + private RaceLogic rl = null; /** * Connection Acceptor Constructor @@ -65,6 +66,11 @@ public class ConnectionAcceptor implements Runnable { return serverPort; } + + public void setRace(RaceLogic rl){ + this.rl = rl; + } + /** * Run the Acceptor */ diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index b98de4dc..38d3092f 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -94,7 +94,9 @@ public class Event { //Create and start race. RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages); + connectionAcceptor.setRace(newRace); new Thread(newRace, "Event.Start()->RaceLogic thread").start(); + } /** diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index 104fa264..a2f74dd7 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -25,7 +25,7 @@ public class MockBoat extends Boat { /** * Stores whether the boat is on autoVMG or not */ - private boolean autoVMG = true; + private boolean autoVMG = false; 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..74c5e95b --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java @@ -0,0 +1,24 @@ +package mock.model.commandFactory; + +import java.util.ArrayDeque; +import java.util.Queue; + +/** + * Wraps multiple commands into a composite to execute queued commands during a frame. + */ +public class CompositeCommand implements Command { + private Queue commands; + + public CompositeCommand() { + this.commands = new ArrayDeque<>(); + } + + public void addCommand(Command command) { + commands.add(command); + } + + @Override + public void execute() { + while(!commands.isEmpty()) commands.remove().execute(); + } +} diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index 150a1da8..8dcf1c48 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -18,6 +18,9 @@ public class TackGybeCommand implements Command { //The refactoring of MockRace will require changes to be made @Override public void execute() { + + boat.setAutoVMG(false); + /*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 64cc6a9f..1a1eeda5 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -18,6 +18,7 @@ public class VMGCommand implements Command { //The refactoring of MockRace will require changes to be made @Override public void execute() { + boat.setAutoVMG(true); /*VMG newVMG = boat.getPolars().calculateVMG( race.getWindDirection(), race.getWindSpeed(), 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..530bf5bc --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java @@ -0,0 +1,37 @@ +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() { + + boat.setAutoVMG(false); + + double wind = race.getWindDirection().degrees(); + double heading = boat.getBearing().degrees(); + + double offset = 3.0; + + offset *= direction; + double headWindDelta = wind - heading; + if ((headWindDelta < 0) || (headWindDelta > 180)) offset *= -1; + + boat.setBearing(Bearing.fromDegrees(heading + offset)); + } +} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java new file mode 100644 index 00000000..3e81cd16 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java @@ -0,0 +1,152 @@ +package visualiser.Controllers; + + +import javafx.application.Platform; +import javafx.beans.property.Property; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Circle; +import shared.model.Bearing; +import shared.model.Wind; +import visualiser.model.VisualiserRace; + +/** + * Controller for the arrow.fxml view. + */ +public class ArrowController { + + + @FXML + private Pane compass; + + @FXML + private StackPane arrowStackPane; + + @FXML + private ImageView arrowImage; + + @FXML + private Circle circle; + + @FXML + private Label northLabel; + + @FXML + private Label windLabel; + + @FXML + private Label speedLabel; + + + /** + * This is the property our arrow control binds to. + */ + private Property wind; + + + /** + * Constructor. + */ + public ArrowController() { + } + + + /** + * Sets which wind property the arrow control should bind to. + * @param wind The wind property to bind to. + */ + public void setWindProperty(Property wind) { + this.wind = wind; + + wind.addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + Platform.runLater(() -> updateWind(newValue)); + } + }); + } + + + /** + * Updates the control to use the new wind value. + * This updates the arrow direction (due to bearing), arrow length (due to speed), and label (due to speed). + * @param wind The wind value to use. + */ + private void updateWind(Wind wind) { + updateWindBearing(wind.getWindDirection()); + updateWindSpeed(wind.getWindSpeed()); + } + + + /** + * Updates the control to account for the new wind speed. + * This changes the length (height) of the wind arrow, and updates the speed label. + * @param speedKnots The new wind speed, in knots. + */ + private void updateWindSpeed(double speedKnots) { + updateWindArrowLength(speedKnots); + updateWindSpeedLabel(speedKnots); + } + + /** + * Updates the length of the wind arrow according to the specified wind speed. + * @param speedKnots Wind speed, in knots. + */ + private void updateWindArrowLength(double speedKnots) { + + //At 2 knots, the arrow reaches its minimum height, and at 30 knots it reaches its maximum height. + double minKnots = 2; + double maxKnots = 30; + double deltaKnots = maxKnots - minKnots; + + double minHeight = 25; + double maxHeight = 75; + double deltaHeight = maxHeight - minHeight; + + //Clamp speed. + if (speedKnots > maxKnots) { + speedKnots = maxKnots; + } else if (speedKnots < minKnots) { + speedKnots = minKnots; + } + + //How far between the knots bounds is the current speed? + double currentDeltaKnots = speedKnots - minKnots; + double currentKnotsScalar = currentDeltaKnots / deltaKnots; + + //Thus, how far between the pixel height bounds should the arrow height be? + double newHeight = minHeight + (currentKnotsScalar * deltaHeight); + + arrowImage.setFitHeight(newHeight); + } + + /** + * Updates the wind speed label according to the specified wind speed. + * @param speedKnots Wind speed, in knots. + */ + private void updateWindSpeedLabel(double speedKnots) { + speedLabel.setText(String.format("%.1fkn", speedKnots)); + } + + + /** + * Updates the control to account for a new wind bearing. + * This rotates the arrow according to the bearing. + * @param bearing The bearing to use to rotate arrow. + */ + private void updateWindBearing(Bearing bearing) { + + //We need to display wind-from, so add 180 degrees. + Bearing fromBearing = Bearing.fromDegrees(bearing.degrees() + 180d); + + //Rotate the wind arrow. + arrowStackPane.setRotate(fromBearing.degrees()); + } + + + + +} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index 5777c06e..f34c57a8 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -5,7 +5,6 @@ import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.chart.LineChart; import javafx.scene.control.*; @@ -24,10 +23,8 @@ import visualiser.gameController.Keys.ControlKey; import visualiser.gameController.Keys.KeyFactory; import visualiser.model.*; -import java.awt.*; import java.io.IOException; import java.net.URL; -import java.text.DecimalFormat; import java.util.ResourceBundle; /** @@ -60,6 +57,11 @@ public class RaceController extends Controller { */ private Sparkline sparkline; + /** + * The arrow controller. + */ + @FXML private ArrowController arrowController; + /** * Service for sending keystrokes to server */ @@ -67,8 +69,18 @@ public class RaceController extends Controller { @FXML private GridPane canvasBase; - @FXML private Pane arrow; + + @FXML private SplitPane race; + + /** + * This is the root node of the arrow control. + */ + @FXML private Pane arrow; + + /** + * This is the pane we place the actual arrow control inside of. + */ @FXML private StackPane arrowPane; @FXML private Label timer; @FXML private Label FPS; @@ -118,15 +130,15 @@ public class RaceController extends Controller { //Fps display. initialiseFps(this.visualiserRace); - //Need to add the included arrow pane to the arrowPane container. - initialiseArrow(); - //Information table. initialiseInfoTable(this.visualiserRace); //Sparkline. initialiseSparkline(this.visualiserRace); + //Arrow. + initialiseArrow(this.visualiserRace); + //Race canvas. initialiseRaceCanvas(this.visualiserRace); @@ -294,7 +306,7 @@ public class RaceController extends Controller { private void initialiseRaceCanvas(VisualiserRace race) { //Create canvas. - raceCanvas = new ResizableRaceCanvas(race, arrow.getChildren().get(0)); + raceCanvas = new ResizableRaceCanvas(race); //Set properties. raceCanvas.setMouseTransparent(true); @@ -367,10 +379,11 @@ public class RaceController extends Controller { /** - * Adds the included arrow pane (see arrow.fxml) to the arrowPane (see race.fxml). + * Initialises the arrow controller with data from the race to observe. + * @param race The race to observe. */ - private void initialiseArrow() { - arrowPane.getChildren().add(arrow); + private void initialiseArrow(VisualiserRace race) { + arrowController.setWindProperty(race.windProperty()); } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index d4c62d11..7469fc26 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -1,23 +1,23 @@ package visualiser.gameController; +import mock.model.RaceLogic; import network.BinaryMessageDecoder; import network.Exceptions.InvalidMessageException; import network.MessageDecoders.BoatActionDecoder; import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; -import visualiser.gameController.Keys.ControlKey; -import visualiser.gameController.Keys.KeyFactory; import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; import java.util.logging.Level; +import java.util.Observable; import java.util.logging.Logger; /** * 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 */ @@ -26,13 +26,23 @@ public class ControllerServer implements Runnable { * Wrapper for input from client */ private DataInputStream inputStream; + /** + * Last received boat action + */ + private BoatActionEnum action; + /** + * + */ + private RaceLogic rc; /** * Initialise server-side controller with live client socket * @param socket to client */ - public ControllerServer(Socket socket) { + public ControllerServer(Socket socket, RaceLogic rc) { this.socket = socket; + this.rc = rc; + this.addObserver(rc); try { this.inputStream = new DataInputStream(this.socket.getInputStream()); } catch (IOException e) { @@ -40,6 +50,10 @@ public class ControllerServer implements Runnable { } } + public BoatActionEnum getAction() { + return action; + } + /** * Wait for controller key input from client and loop. */ @@ -58,7 +72,11 @@ public class ControllerServer implements Runnable { try { boatActionDecoder.decode(encodedMessage.getMessageBody()); BoatAction boatAction = boatActionDecoder.getMessage(); - System.out.println("Received key: " + boatAction.getBoatAction()); + action = boatActionDecoder.getBoatAction(); + + // Notify observers of most recent action + this.notifyObservers(); + this.setChanged(); } catch (InvalidMessageException e) { Logger.getGlobal().log(Level.WARNING, "Could not decode BoatAction message.", e); 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/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index 7664f854..e9b4c31c 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -12,7 +12,6 @@ import shared.model.GPSCoordinate; import shared.model.Mark; import shared.model.RaceClock; -import java.time.Duration; import java.util.List; /** @@ -39,12 +38,6 @@ public class ResizableRaceCanvas extends ResizableCanvas { */ private VisualiserRace visualiserRace; - /** - * The background of the race. - * We render the background whenever the race boundary changes, or the screen size changes. - */ - private Image background; - private boolean annoName = true; private boolean annoAbbrev = true; @@ -54,22 +47,15 @@ public class ResizableRaceCanvas extends ResizableCanvas { private boolean annoTimeSinceLastMark = true; - /** - * The wind arrow node. - */ - private Node arrow; - /** * Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRace}. * @param visualiserRace The race that data is read from in order to be drawn. - * @param arrow The wind arrow's node. */ - public ResizableRaceCanvas(VisualiserRace visualiserRace, Node arrow) { + public ResizableRaceCanvas(VisualiserRace visualiserRace) { super(); this.visualiserRace = visualiserRace; - this.arrow = arrow; RaceDataSource raceData = visualiserRace.getRaceDataSource(); @@ -375,32 +361,6 @@ public class ResizableRaceCanvas extends ResizableCanvas { - /** - * Displays an arrow representing wind direction on the Canvas. - * This function accepts a wind-to bearing, but displays a wind-from bearing. - * - * @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up). - * @see GraphCoordinate - */ - private void displayWindArrow(double angle) { - - //We need to display wind-from, so add 180 degrees. - angle += 180d; - - //Get it within [0, 360). - while (angle >= 360d) { - angle -= 360d; - } - - //Rotate the wind arrow. - if (arrow != null && arrow.getRotate() != angle) { - arrow.setRotate(angle); - } - } - - - - /** * Draws all of the {@link Mark}s on the canvas. */ @@ -440,13 +400,9 @@ public class ResizableRaceCanvas extends ResizableCanvas { this.map.setWidth((int) getWidth()); this.map.setHeight((int) getHeight()); - //Redraw the boundary. - redrawBoundaryImage(); - //Draw the race. drawRace(); - } @@ -459,15 +415,13 @@ 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. @@ -487,9 +441,6 @@ public class ResizableRaceCanvas extends ResizableCanvas { //Draw the boundary. gc.fillPolygon(xpoints, ypoints, xpoints.length); - //Render boundary to image. - this.background = snapshot(null, null); - } /** @@ -511,18 +462,6 @@ public class ResizableRaceCanvas extends ResizableCanvas { //Marks. drawMarks(); - //Wind arrow. This rotates the wind arrow node. - displayWindArrow(this.visualiserRace.getWindDirection().degrees()); - - } - - - /** - * Draws the race boundary image onto the canvas. - * See {@link #background}. - */ - private void drawBoundary() { - gc.drawImage(this.background, 0, 0); } 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/main/resources/visualiser/scenes/arrow.fxml b/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml index 6e8a88b5..4057753d 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml @@ -1,34 +1,58 @@ - - - - - - - + + + + + + + + + + + - + + + + + + + + + - + - - - - - + + + + + + + + + + + + - - - - + - + diff --git a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml index 76da5379..159d725c 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml @@ -16,7 +16,6 @@ - @@ -76,7 +75,11 @@ - + + + + + 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()); + + } + + } +} 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..e5d147d9 --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java @@ -0,0 +1,58 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; +import mock.model.MockRace; +import network.Messages.Enums.BoatActionEnum; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import shared.model.Bearing; +import shared.model.Boat; +import shared.model.Race; +import visualiser.model.VisualiserRace; + +import static org.mockito.Mockito.when; +import static org.testng.Assert.*; +import static org.mockito.Mockito.mock; + +/** + * Created by connortaylorbrown on 4/08/17. + */ +public class WindCommandTest { + private MockRace race; + private MockBoat boat; + private Command upwind; + private Command downwind; + private double initial; + + private double offset = 3.0; + + @Before + public void setUp() { + race = mock(MockRace.class); + boat = new MockBoat(0, "Bob", "NZ", null); + + when(race.getWindDirection()).thenReturn(Bearing.fromDegrees(0.0)); + boat.setBearing(Bearing.fromDegrees(45.0)); + + upwind = CommandFactory.createCommand(race, boat, BoatActionEnum.UPWIND); + downwind = CommandFactory.createCommand(race, boat, BoatActionEnum.DOWNWIND); + + initial = boat.getBearing().degrees(); + } + + /** + * Ensure the difference between initial and final angle is 3 degrees + */ + @Test + public void upwindCommandDecreasesAngle() { + upwind.execute(); + assertEquals(initial - boat.getBearing().degrees(), -offset, 1e-5); + } + + @Test + public void downwindCommandIncreasesAngle() { + downwind.execute(); + assertEquals(initial - boat.getBearing().degrees(), offset, 1e-5); + } +} \ No newline at end of file