diff --git a/racevisionGame/pom.xml b/racevisionGame/pom.xml
index f77e5ea3..b0f6d203 100644
--- a/racevisionGame/pom.xml
+++ b/racevisionGame/pom.xml
@@ -83,89 +83,40 @@
-
-
-
- mock
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.5.1
-
-
- org.apache.maven.plugins
- maven-shade-plugin
- 2.4.3
-
-
-
-
- visualiser.app.App
- ${maven.compiler.source}
- ${maven.compiler.target}
-
-
-
-
-
-
- package
-
- shade
-
-
-
-
-
-
-
-
-
-
- visualiser
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.5.1
-
-
- org.apache.maven.plugins
- maven-shade-plugin
- 2.4.3
-
-
-
-
- visualiser.app.App
- ${maven.compiler.source}
- ${maven.compiler.target}
-
-
-
-
-
-
- package
-
- shade
-
-
-
-
-
-
-
-
-
-
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.5.1
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.4.3
+
+
+
+
+ visualiser.app.App
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+
diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java
index 120da194..ba22fef0 100644
--- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java
+++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java
@@ -1,12 +1,11 @@
package mock.app;
-import mock.enums.ConnectionStateEnum;
+import mock.model.RaceLogic;
import mock.model.ClientConnection;
import mock.model.SourceIdAllocator;
import mock.model.commandFactory.CompositeCommand;
import network.Messages.Enums.XMLMessageType;
import network.Messages.LatestMessages;
-import network.Messages.RaceSnapshot;
import network.Messages.XMLMessage;
import java.io.IOException;
@@ -65,19 +64,23 @@ public class ConnectionAcceptor implements Runnable {
private short boatXMLSequenceNumber;
//regatta xml sequence number
private short regattaXMLSequenceNumber;
+ //
+ private RaceLogic raceLogic = null;
/**
* Connection Acceptor Constructor
* @param latestMessages Latest messages to be sent
* @param compositeCommand Collection of commands for race to execute.
* @param sourceIdAllocator Object used to allocate source IDs for clients.
+ * @param raceLogic The race the client will connect to.
* @throws IOException if a server socket cannot be instantiated.
*/
- public ConnectionAcceptor(LatestMessages latestMessages, CompositeCommand compositeCommand, SourceIdAllocator sourceIdAllocator) throws IOException {
+ public ConnectionAcceptor(LatestMessages latestMessages, CompositeCommand compositeCommand, SourceIdAllocator sourceIdAllocator, RaceLogic raceLogic) throws IOException {
this.latestMessages = latestMessages;
this.compositeCommand = compositeCommand;
this.sourceIdAllocator = sourceIdAllocator;
+ this.raceLogic = raceLogic;
this.serverSocket = new ServerSocket(serverPort);
CheckClientConnection checkClientConnection = new CheckClientConnection(clientConnections);
@@ -93,6 +96,8 @@ public class ConnectionAcceptor implements Runnable {
return serverPort;
}
+
+
/**
* Run the Acceptor
*/
@@ -103,11 +108,13 @@ public class ConnectionAcceptor implements Runnable {
try {
- System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE
Socket mockSocket = serverSocket.accept();
- ClientConnection clientConnection = new ClientConnection(mockSocket, sourceIdAllocator, latestMessages, compositeCommand);
+ Logger.getGlobal().log(Level.INFO, String.format("Client connected. client ip/port = %s. Local ip/port = %s.", mockSocket.getRemoteSocketAddress(), mockSocket.getLocalSocketAddress()));
+
+
+ ClientConnection clientConnection = new ClientConnection(mockSocket, sourceIdAllocator, latestMessages, compositeCommand, raceLogic);
clientConnections.add(clientConnection);
@@ -153,6 +160,7 @@ public class ConnectionAcceptor implements Runnable {
for (ClientConnection client : clientConnections) {
if (!client.isAlive()) {
connections.remove(client);
+ client.terminate();
Logger.getGlobal().log(Level.WARNING, "CheckClientConnection is removing the dead connection: " + client);
}
diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java
index 7d7c940b..5c6dd193 100644
--- a/racevisionGame/src/main/java/mock/app/Event.java
+++ b/racevisionGame/src/main/java/mock/app/Event.java
@@ -2,10 +2,7 @@ package mock.app;
import mock.dataInput.PolarParser;
import mock.exceptions.EventConstructionException;
-import mock.model.MockRace;
-import mock.model.Polars;
-import mock.model.RaceLogic;
-import mock.model.SourceIdAllocator;
+import mock.model.*;
import mock.model.commandFactory.CompositeCommand;
import network.Messages.LatestMessages;
import network.Messages.RaceSnapshot;
@@ -15,6 +12,7 @@ import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
+import shared.model.Bearing;
import shared.model.Constants;
import javax.xml.transform.TransformerException;
@@ -67,15 +65,27 @@ public class Event {
/**
* Constructs an event, using various XML files.
+ * @param singlePlayer Whether or not to create a single player event.
* @throws EventConstructionException Thrown if we cannot create an Event for any reason.
*/
- public Event() throws EventConstructionException {
+ public Event(boolean singlePlayer) throws EventConstructionException {
+
+ singlePlayer = false;
+
+ String raceXMLFile = "mock/mockXML/raceTest.xml";
+ String boatsXMLFile = "mock/mockXML/boatTest.xml";
+ String regattaXMLFile = "mock/mockXML/regattaTest.xml";
+
+ if (singlePlayer) {
+ raceXMLFile = "mock/mockXML/raceSinglePlayer.xml";
+ boatsXMLFile = "mock/mockXML/boatsSinglePlayer.xml";
+ }
//Read XML files.
try {
- this.raceXML = getRaceXMLAtCurrentTime(XMLReader.readXMLFileToString("mock/mockXML/raceTest.xml", StandardCharsets.UTF_8));
- this.boatXML = XMLReader.readXMLFileToString("mock/mockXML/boatsSinglePlayer.xml", StandardCharsets.UTF_8);
- this.regattaXML = XMLReader.readXMLFileToString("mock/mockXML/regattaTest.xml", StandardCharsets.UTF_8);
+ this.raceXML = getRaceXMLAtCurrentTime(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
+ this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);
+ this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, StandardCharsets.UTF_8);
} catch (TransformerException | XMLReaderException e) {
throw new EventConstructionException("Could not read XML files.", e);
@@ -98,17 +108,48 @@ public class Event {
}
- //Create connection acceptor.
this.sourceIdAllocator = new SourceIdAllocator(raceDataSource.getParticipants());
this.compositeCommand = new CompositeCommand();
this.latestMessages = new LatestMessages();
+ //Create and start race.
+ WindGenerator windGenerator = new RandomWindGenerator(
+ Bearing.fromDegrees(225),
+ Bearing.fromDegrees(215),
+ Bearing.fromDegrees(235),
+ 12d,
+ 8d,
+ 16d );
+ RaceLogic newRace = new RaceLogic(
+ new MockRace(
+ boatDataSource,
+ raceDataSource,
+ regattaDataSource,
+ this.latestMessages,
+ this.boatPolars,
+ Constants.RaceTimeScale,
+ windGenerator ),
+ this.latestMessages,
+ this.compositeCommand);
+
+ new Thread(newRace, "Event.Start()->RaceLogic thread").start();
+
+
+ //Create connection acceptor.
+
try {
- this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator);
+ this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace);
} catch (IOException e) {
throw new EventConstructionException("Could not create ConnectionAcceptor.", e);
}
+
+
+ new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread").start();
+
+ sendXMLs();
+
+
}
@@ -125,16 +166,8 @@ public class Event {
* Sends the initial race data and then begins race simulation.
*/
public void start() {
- new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread").start();
-
- sendXMLs();
-
- //Create and start race.
- RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages, this.compositeCommand);
-
- new Thread(newRace, "Event.Start()->RaceLogic thread").start();
}
/**
diff --git a/racevisionGame/src/main/java/mock/exceptions/CommandConstructionException.java b/racevisionGame/src/main/java/mock/exceptions/CommandConstructionException.java
new file mode 100644
index 00000000..f1b286f7
--- /dev/null
+++ b/racevisionGame/src/main/java/mock/exceptions/CommandConstructionException.java
@@ -0,0 +1,24 @@
+package mock.exceptions;
+
+/**
+ * An exception thrown when we cannot create a command for some reasn (e.g., uknown action type).
+ */
+public class CommandConstructionException extends Exception {
+
+ /**
+ * Constructs the exception with a given message.
+ * @param message Message to store.
+ */
+ public CommandConstructionException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs the exception with a given message and cause.
+ * @param message Message to store.
+ * @param cause Cause to store.
+ */
+ public CommandConstructionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/racevisionGame/src/main/java/mock/model/ClientConnection.java b/racevisionGame/src/main/java/mock/model/ClientConnection.java
index 71d85348..23e00819 100644
--- a/racevisionGame/src/main/java/mock/model/ClientConnection.java
+++ b/racevisionGame/src/main/java/mock/model/ClientConnection.java
@@ -36,6 +36,11 @@ public class ClientConnection implements Runnable {
*/
private HeartBeatService heartBeatService;
+ /**
+ * The thread the {@link HeartBeatService} runs on.
+ */
+ private Thread heartBeatThread;
+
/**
* Used to allocate source ID to client, if they request to participate.
@@ -53,16 +58,31 @@ public class ClientConnection implements Runnable {
*/
private CompositeCommand compositeCommand;
+ /**
+ * The race the client is connected to.
+ */
+ private RaceLogic raceLogic;
+
/**
* Used to send the race snapshot to client.
*/
private MockOutput mockOutput;
+ /**
+ * The thread the {@link MockOutput} runs on.
+ */
+ private Thread mockOutputThread;
+
/**
* Used to receive client input, and turn it into commands.
*/
private ControllerServer controllerServer;
+ /**
+ * The thread the {@link ControllerServer} runs on.
+ */
+ private Thread controllerServerThread;
+
/**
* Used to write messages to socket.
@@ -100,13 +120,15 @@ public class ClientConnection implements Runnable {
* @param sourceIdAllocator Used to allocate a source ID for the client.
* @param latestMessages Latest race snapshot to send to client.
* @param compositeCommand Collection of commands for race to execute.
+ * @param raceLogic The race the client is connected to.
* @throws IOException Thrown if there is a problem with the client socket.
*/
- public ClientConnection(Socket socket, SourceIdAllocator sourceIdAllocator, LatestMessages latestMessages, CompositeCommand compositeCommand) throws IOException {
+ public ClientConnection(Socket socket, SourceIdAllocator sourceIdAllocator, LatestMessages latestMessages, CompositeCommand compositeCommand, RaceLogic raceLogic) throws IOException {
this.socket = socket;
this.sourceIdAllocator = sourceIdAllocator;
this.latestMessages = latestMessages;
this.compositeCommand = compositeCommand;
+ this.raceLogic = raceLogic;
this.outputQueue = new LinkedBlockingQueue<>();
this.inputQueue = new LinkedBlockingQueue<>();
@@ -120,7 +142,8 @@ public class ClientConnection implements Runnable {
this.heartBeatService = new HeartBeatService(outputQueue);
- new Thread(heartBeatService, "ClientConnection()->HeartBeatService thread " + heartBeatService).start();
+ this.heartBeatThread = new Thread(heartBeatService, "ClientConnection()->HeartBeatService thread " + heartBeatService);
+ this.heartBeatThread.start();
}
@@ -145,7 +168,7 @@ public class ClientConnection implements Runnable {
* @throws HandshakeException Thrown if something goes wrong with the handshake.
* @throws SourceIDAllocationException Thrown if we cannot allocate a sourceID.
*/
- private void handshake() throws SourceIDAllocationException, HandshakeException {
+ private void handshake() throws SourceIDAllocationException, HandshakeException {
//This function is a bit messy, and could probably be refactored a bit.
@@ -162,16 +185,20 @@ public class ClientConnection implements Runnable {
allocatedSourceID = sourceIdAllocator.allocateSourceID();
- this.controllerServer = new ControllerServer(compositeCommand, inputQueue, allocatedSourceID);
- new Thread(controllerServer, "ClientConnection.run()->ControllerServer thread" + controllerServer).start();
+ this.controllerServer = new ControllerServer(compositeCommand, inputQueue, allocatedSourceID, raceLogic.getRace());
+ this.controllerServerThread = new Thread(controllerServer, "ClientConnection.run()->ControllerServer thread" + controllerServer);
+ this.controllerServerThread.start();
}
- this.mockOutput = new MockOutput(latestMessages, outputQueue);
- new Thread(mockOutput, "ClientConnection.run()->MockOutput thread" + mockOutput).start();
sendJoinAcceptanceMessage(allocatedSourceID);
+ this.mockOutput = new MockOutput(latestMessages, outputQueue);
+ this.mockOutputThread = new Thread(mockOutput, "ClientConnection.run()->MockOutput thread" + mockOutput);
+ this.mockOutputThread.start();
+
+
connectionState = ConnectionStateEnum.CONNECTED;
}
@@ -240,4 +267,22 @@ public class ClientConnection implements Runnable {
}
+ /**
+ * Terminates this connection.
+ */
+ public void terminate() {
+
+ if (this.heartBeatThread != null) {
+ this.heartBeatThread.interrupt();
+ }
+
+ if (this.mockOutputThread != null) {
+ this.mockOutputThread.interrupt();
+ }
+
+ if (this.controllerServerThread != null) {
+ this.controllerServerThread.interrupt();
+ }
+ }
+
}
diff --git a/racevisionGame/src/main/java/mock/model/ConstantWindGenerator.java b/racevisionGame/src/main/java/mock/model/ConstantWindGenerator.java
new file mode 100644
index 00000000..ae14daac
--- /dev/null
+++ b/racevisionGame/src/main/java/mock/model/ConstantWindGenerator.java
@@ -0,0 +1,54 @@
+package mock.model;
+
+
+import shared.model.Bearing;
+import shared.model.Wind;
+
+import java.util.Random;
+
+/**
+ * This class generates Wind objects for use in a MockRace.
+ * Initialised with a baseline wind speed and direction, and keeps it constant.
+ */
+public class ConstantWindGenerator implements WindGenerator {
+
+ /**
+ * The bearing the wind direction starts at.
+ */
+ private Bearing windBaselineBearing;
+
+
+ /**
+ * The speed the wind starts at, in knots.
+ */
+ private double windBaselineSpeed;
+
+
+
+ /**
+ * Creates a constant wind generator, with a baseline wind speed and direction.
+ * @param windBaselineBearing Baseline wind direction.
+ * @param windBaselineSpeed Baseline wind speed, in knots.
+ */
+ public ConstantWindGenerator(Bearing windBaselineBearing, double windBaselineSpeed) {
+
+ this.windBaselineBearing = windBaselineBearing;
+ this.windBaselineSpeed = windBaselineSpeed;
+
+ }
+
+
+ @Override
+ public Wind generateBaselineWind() {
+ return new Wind(windBaselineBearing, windBaselineSpeed);
+ }
+
+
+ @Override
+ public Wind generateNextWind(Wind currentWind) {
+
+ return generateBaselineWind();
+
+ }
+
+}
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/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java
index e389f474..94fc8792 100644
--- a/racevisionGame/src/main/java/mock/model/MockRace.java
+++ b/racevisionGame/src/main/java/mock/model/MockRace.java
@@ -2,7 +2,6 @@ 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;
@@ -13,7 +12,6 @@ import shared.model.Bearing;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
-import java.time.temporal.TemporalUnit;
import java.util.*;
import static java.lang.Math.cos;
@@ -59,7 +57,7 @@ public class MockRace extends Race {
* @param polars The polars table to be used for boat simulation.
* @param timeScale The timeScale for the race. See {@link Constants#RaceTimeScale}.
*/
- public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars, int timeScale) {
+ public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars, int timeScale, WindGenerator windGenerator) {
super(boatDataSource, raceDataSource, regattaDataSource, latestMessages);
@@ -69,14 +67,8 @@ 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),
- Bearing.fromDegrees(215),
- Bearing.fromDegrees(235),
- 12d,
- 8d,
- 16d );
+
+ this.windGenerator = windGenerator;
//Wind.
this.setWind(windGenerator.generateBaselineWind());
diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java
index 42bea8ef..7b314d0f 100644
--- a/racevisionGame/src/main/java/mock/model/RaceLogic.java
+++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java
@@ -2,14 +2,15 @@ package mock.model;
import javafx.animation.AnimationTimer;
import mock.model.commandFactory.CompositeCommand;
+import mock.model.commandFactory.CommandFactory;
+import network.Messages.Enums.BoatActionEnum;
import network.Messages.Enums.BoatStatusEnum;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.LatestMessages;
+import shared.model.RunnableWithFramePeriod;
-
-public class RaceLogic implements Runnable {
-
+public class RaceLogic implements RunnableWithFramePeriod {
/**
* State of current race modified by this object
*/
@@ -66,6 +67,7 @@ public class RaceLogic implements Runnable {
//Parse the race snapshot.
server.parseSnapshot();
+
// Change wind direction
race.changeWindDirection();
@@ -113,7 +115,7 @@ public class RaceLogic implements Runnable {
currentTime = System.currentTimeMillis();
//Execute commands from clients.
- commands.execute(race);
+ commands.execute();
//Update race time.
race.updateRaceTime(currentTime);
@@ -172,4 +174,12 @@ public class RaceLogic implements Runnable {
}
};
+
+ /**
+ * Returns the race state that this RaceLogic is simulating.
+ * @return Race state this RaceLogic is simulating.
+ */
+ public MockRace getRace() {
+ return race;
+ }
}
diff --git a/racevisionGame/src/main/java/mock/model/RandomWindGenerator.java b/racevisionGame/src/main/java/mock/model/RandomWindGenerator.java
new file mode 100644
index 00000000..4f981b8d
--- /dev/null
+++ b/racevisionGame/src/main/java/mock/model/RandomWindGenerator.java
@@ -0,0 +1,242 @@
+package mock.model;
+
+
+import shared.model.Bearing;
+import shared.model.Wind;
+
+import java.util.Random;
+
+/**
+ * This class generates Wind objects for use in a MockRace.
+ * Bounds on bearing and speed can be specified.
+ * Wind can be completely random, or random incremental change.
+ */
+public class RandomWindGenerator implements WindGenerator {
+
+ /**
+ * The bearing the wind direction starts at.
+ */
+ private Bearing windBaselineBearing;
+
+ /**
+ * The lower bearing angle that the wind may have.
+ */
+ private Bearing windBearingLowerBound;
+
+ /**
+ * The upper bearing angle that the wind may have.
+ */
+ private Bearing windBearingUpperBound;
+
+
+
+ /**
+ * The speed the wind starts at, in knots.
+ */
+ private double windBaselineSpeed;
+
+ /**
+ * The lower speed that the wind may have, in knots.
+ */
+ private double windSpeedLowerBound;
+
+ /**
+ * The upper speed that the wind may have, in knots.
+ */
+ private double windSpeedUpperBound;
+
+
+ /**
+ * Creates a wind generator, with a baseline, lower bound, and upper bound, for the wind speed and direction.
+ * @param windBaselineBearing Baseline wind direction.
+ * @param windBearingLowerBound Lower bound for wind direction.
+ * @param windBearingUpperBound Upper bound for wind direction.
+ * @param windBaselineSpeed Baseline wind speed, in knots.
+ * @param windSpeedLowerBound Lower bound for wind speed, in knots.
+ * @param windSpeedUpperBound Upper bound for wind speed, in knots.
+ */
+ public RandomWindGenerator(Bearing windBaselineBearing, Bearing windBearingLowerBound, Bearing windBearingUpperBound, double windBaselineSpeed, double windSpeedLowerBound, double windSpeedUpperBound) {
+
+ this.windBaselineBearing = windBaselineBearing;
+ this.windBearingLowerBound = windBearingLowerBound;
+ this.windBearingUpperBound = windBearingUpperBound;
+ this.windBaselineSpeed = windBaselineSpeed;
+ this.windSpeedLowerBound = windSpeedLowerBound;
+ this.windSpeedUpperBound = windSpeedUpperBound;
+
+ }
+
+
+ @Override
+ public Wind generateBaselineWind() {
+ return new Wind(windBaselineBearing, windBaselineSpeed);
+ }
+
+ /**
+ * Generates a random Wind object, that is within the provided bounds.
+ * @return Generated wind object.
+ */
+ public Wind generateRandomWind() {
+
+ double windSpeed = generateRandomWindSpeed();
+ Bearing windBearing = generateRandomWindBearing();
+
+ return new Wind(windBearing, windSpeed);
+
+ }
+
+ /**
+ * Generates a random wind speed within the specified bounds. In knots.
+ * @return Wind speed, in knots.
+ */
+ private double generateRandomWindSpeed() {
+
+ double randomSpeedKnots = generateRandomValueInBounds(windSpeedLowerBound, windSpeedUpperBound);
+
+ return randomSpeedKnots;
+ }
+
+
+ /**
+ * Generates a random wind bearing within the specified bounds.
+ * @return Wind bearing.
+ */
+ private Bearing generateRandomWindBearing() {
+
+ double randomBearingDegrees = generateRandomValueInBounds(windBearingLowerBound.degrees(), windBearingUpperBound.degrees());
+
+ return Bearing.fromDegrees(randomBearingDegrees);
+ }
+
+
+ /**
+ * Generates a random value within a specified interval.
+ * @param lowerBound The lower bound of the interval.
+ * @param upperBound The upper bound of the interval.
+ * @return A random value within the interval.
+ */
+ private static double generateRandomValueInBounds(double lowerBound, double upperBound) {
+
+ float proportion = new Random().nextFloat();
+
+ double delta = upperBound - lowerBound;
+
+ double amount = delta * proportion;
+
+ double finalAmount = amount + lowerBound;
+
+ return finalAmount;
+
+ }
+
+
+ /**
+ * Generates a new value within an interval, given a start value, chance to change, and change amount.
+ * @param lowerBound Lower bound of interval.
+ * @param upperBound Upper bound of interval.
+ * @param currentValue The current value to change.
+ * @param changeAmount The amount to change by.
+ * @param chanceToChange The change to actually change the value.
+ * @return The new value.
+ */
+ private static double generateNextValueInBounds(double lowerBound, double upperBound, double currentValue, double changeAmount, double chanceToChange) {
+
+ float chance = new Random().nextFloat();
+
+
+ if (chance <= chanceToChange) {
+ currentValue += changeAmount;
+
+ } else if (chance <= (2 * chanceToChange)) {
+ currentValue -= changeAmount;
+
+ }
+
+ currentValue = clamp(lowerBound, upperBound, currentValue);
+
+ return currentValue;
+
+ }
+
+
+ @Override
+ public Wind generateNextWind(Wind currentWind) {
+
+ double windSpeed = generateNextWindSpeed(currentWind.getWindSpeed());
+ Bearing windBearing = generateNextWindBearing(currentWind.getWindDirection());
+
+ return new Wind(windBearing, windSpeed);
+
+ }
+
+
+ /**
+ * Generates the next wind speed to use.
+ * @param windSpeed Current wind speed, in knots.
+ * @return Next wind speed, in knots.
+ */
+ private double generateNextWindSpeed(double windSpeed) {
+
+ double chanceToChange = 0.2;
+ double changeAmount = 0.1;
+
+ double nextWindSpeed = generateNextValueInBounds(
+ windSpeedLowerBound,
+ windSpeedUpperBound,
+ windSpeed,
+ changeAmount,
+ chanceToChange);
+
+ return nextWindSpeed;
+ }
+
+
+ /**
+ * Generates the next wind speed to use.
+ * @param windBearing Current wind bearing.
+ * @return Next wind speed.
+ */
+ private Bearing generateNextWindBearing(Bearing windBearing) {
+
+ double chanceToChange = 0.2;
+ double changeAmount = 0.5;
+
+ double nextWindBearingDegrees = generateNextValueInBounds(
+ windBearingLowerBound.degrees(),
+ windBearingUpperBound.degrees(),
+ windBearing.degrees(),
+ changeAmount,
+ chanceToChange);
+
+ return Bearing.fromDegrees(nextWindBearingDegrees);
+ }
+
+
+
+
+
+ /**
+ * Clamps a value to be within an interval.
+ * @param lower Lower bound of the interval.
+ * @param upper Upper bound of the interval.
+ * @param value Value to clamp.
+ * @return The clamped value.
+ */
+ private static double clamp(double lower, double upper, double value) {
+
+ if (value > upper) {
+ value = upper;
+
+ } else if (value < lower) {
+ value = lower;
+
+ }
+
+ return value;
+ }
+
+
+
+
+
+}
diff --git a/racevisionGame/src/main/java/mock/model/WindGenerator.java b/racevisionGame/src/main/java/mock/model/WindGenerator.java
index 30fd24b4..8285d5d3 100644
--- a/racevisionGame/src/main/java/mock/model/WindGenerator.java
+++ b/racevisionGame/src/main/java/mock/model/WindGenerator.java
@@ -1,249 +1,29 @@
package mock.model;
-
-import shared.model.Bearing;
import shared.model.Wind;
-import java.util.Random;
/**
- * This class generates Wind objects for use in a MockRace.
- * Bounds on bearing and speed can be specified.
- * Wind can be completely random, or random incremental change.
+ * Interface for wind generators. It allows for generating a baseline wind, and subsequent winds.
*/
-public class WindGenerator {
-
- /**
- * The bearing the wind direction starts at.
- */
- private Bearing windBaselineBearing;
-
- /**
- * The lower bearing angle that the wind may have.
- */
- private Bearing windBearingLowerBound;
-
- /**
- * The upper bearing angle that the wind may have.
- */
- private Bearing windBearingUpperBound;
-
-
-
- /**
- * The speed the wind starts at, in knots.
- */
- private double windBaselineSpeed;
-
- /**
- * The lower speed that the wind may have, in knots.
- */
- private double windSpeedLowerBound;
-
- /**
- * The upper speed that the wind may have, in knots.
- */
- private double windSpeedUpperBound;
-
-
- /**
- * Creates a wind generator, with a baseline, lower bound, and upper bound, for the wind speed and direction.
- * @param windBaselineBearing Baseline wind direction.
- * @param windBearingLowerBound Lower bound for wind direction.
- * @param windBearingUpperBound Upper bound for wind direction.
- * @param windBaselineSpeed Baseline wind speed, in knots.
- * @param windSpeedLowerBound Lower bound for wind speed, in knots.
- * @param windSpeedUpperBound Upper bound for wind speed, in knots.
- */
- public WindGenerator(Bearing windBaselineBearing, Bearing windBearingLowerBound, Bearing windBearingUpperBound, double windBaselineSpeed, double windSpeedLowerBound, double windSpeedUpperBound) {
-
- this.windBaselineBearing = windBaselineBearing;
- this.windBearingLowerBound = windBearingLowerBound;
- this.windBearingUpperBound = windBearingUpperBound;
- this.windBaselineSpeed = windBaselineSpeed;
- this.windSpeedLowerBound = windSpeedLowerBound;
- this.windSpeedUpperBound = windSpeedUpperBound;
+public interface WindGenerator {
- }
/**
* Generates a wind object using the baseline wind speed and bearing.
* @return Baseline wind object.
*/
- public Wind generateBaselineWind() {
- return new Wind(windBaselineBearing, windBaselineSpeed);
- }
-
- /**
- * Generates a random Wind object, that is within the provided bounds.
- * @return Generated wind object.
- */
- public Wind generateRandomWind() {
-
- double windSpeed = generateRandomWindSpeed();
- Bearing windBearing = generateRandomWindBearing();
-
- return new Wind(windBearing, windSpeed);
-
- }
-
- /**
- * Generates a random wind speed within the specified bounds. In knots.
- * @return Wind speed, in knots.
- */
- private double generateRandomWindSpeed() {
-
- double randomSpeedKnots = generateRandomValueInBounds(windSpeedLowerBound, windSpeedUpperBound);
-
- return randomSpeedKnots;
- }
-
-
- /**
- * Generates a random wind bearing within the specified bounds.
- * @return Wind bearing.
- */
- private Bearing generateRandomWindBearing() {
-
- double randomBearingDegrees = generateRandomValueInBounds(windBearingLowerBound.degrees(), windBearingUpperBound.degrees());
-
- return Bearing.fromDegrees(randomBearingDegrees);
- }
-
-
- /**
- * Generates a random value within a specified interval.
- * @param lowerBound The lower bound of the interval.
- * @param upperBound The upper bound of the interval.
- * @return A random value within the interval.
- */
- private static double generateRandomValueInBounds(double lowerBound, double upperBound) {
+ Wind generateBaselineWind();
- float proportion = new Random().nextFloat();
-
- double delta = upperBound - lowerBound;
-
- double amount = delta * proportion;
-
- double finalAmount = amount + lowerBound;
-
- return finalAmount;
-
- }
-
-
- /**
- * Generates a new value within an interval, given a start value, chance to change, and change amount.
- * @param lowerBound Lower bound of interval.
- * @param upperBound Upper bound of interval.
- * @param currentValue The current value to change.
- * @param changeAmount The amount to change by.
- * @param chanceToChange The change to actually change the value.
- * @return The new value.
- */
- private static double generateNextValueInBounds(double lowerBound, double upperBound, double currentValue, double changeAmount, double chanceToChange) {
-
- float chance = new Random().nextFloat();
-
-
- if (chance <= chanceToChange) {
- currentValue += changeAmount;
-
- } else if (chance <= (2 * chanceToChange)) {
- currentValue -= changeAmount;
-
- }
-
- currentValue = clamp(lowerBound, upperBound, currentValue);
-
- return currentValue;
-
- }
/**
- * Generates the next Wind object, that is within the provided bounds. This randomly increases or decreases the wind's speed and bearing.
+ * Generates the next Wind object, according to the implementation of the wind generator.
* @param currentWind The current wind to change. This is not modified.
* @return Generated wind object.
*/
- public Wind generateNextWind(Wind currentWind) {
-
- double windSpeed = generateNextWindSpeed(currentWind.getWindSpeed());
- Bearing windBearing = generateNextWindBearing(currentWind.getWindDirection());
-
- return new Wind(windBearing, windSpeed);
-
- }
-
-
- /**
- * Generates the next wind speed to use.
- * @param windSpeed Current wind speed, in knots.
- * @return Next wind speed, in knots.
- */
- private double generateNextWindSpeed(double windSpeed) {
-
- double chanceToChange = 0.2;
- double changeAmount = 0.1;
-
- double nextWindSpeed = generateNextValueInBounds(
- windSpeedLowerBound,
- windSpeedUpperBound,
- windSpeed,
- changeAmount,
- chanceToChange);
-
- return nextWindSpeed;
- }
-
-
- /**
- * Generates the next wind speed to use.
- * @param windBearing Current wind bearing.
- * @return Next wind speed.
- */
- private Bearing generateNextWindBearing(Bearing windBearing) {
-
- double chanceToChange = 0.2;
- double changeAmount = 0.5;
-
- double nextWindBearingDegrees = generateNextValueInBounds(
- windBearingLowerBound.degrees(),
- windBearingUpperBound.degrees(),
- windBearing.degrees(),
- changeAmount,
- chanceToChange);
-
- return Bearing.fromDegrees(nextWindBearingDegrees);
- }
-
-
-
-
-
- /**
- * Clamps a value to be within an interval.
- * @param lower Lower bound of the interval.
- * @param upper Upper bound of the interval.
- * @param value Value to clamp.
- * @return The clamped value.
- */
- private static double clamp(double lower, double upper, double value) {
-
- if (value > upper) {
- value = upper;
-
- } else if (value < lower) {
- value = lower;
-
- }
-
- return value;
- }
-
-
-
+ Wind generateNextWind(Wind currentWind);
}
diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java
index 6e87f11b..4edb87c5 100644
--- a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java
+++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java
@@ -1,8 +1,10 @@
package mock.model.commandFactory;
+import mock.exceptions.CommandConstructionException;
import mock.model.MockBoat;
import mock.model.MockRace;
-import network.Messages.Enums.BoatActionEnum;
+import network.Messages.BoatAction;
+import shared.exceptions.BoatNotFoundException;
/**
* Factory class for Command objects
@@ -11,15 +13,28 @@ 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
+ * @return The command to execute the given action.
+ * @throws CommandConstructionException Thrown if the command cannot be constructed (e.g., unknown action type).
*/
- public static Command createCommand(MockRace race, MockBoat boat, BoatActionEnum action) {
- switch(action) {
+ public static Command createCommand(MockRace race, BoatAction action) throws CommandConstructionException {
+
+ MockBoat boat = null;
+ try {
+ boat = race.getBoat(action.getSourceID());
+
+ } catch (BoatNotFoundException e) {
+ throw new CommandConstructionException("Could not create command for BoatAction: " + action + ". Boat with sourceID: " + action.getSourceID() + " not found.", e);
+
+ }
+
+ switch(action.getBoatAction()) {
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
+ case UPWIND: return new WindCommand(race, boat, true);
+ case DOWNWIND: return new WindCommand(race, boat, false);
+
+ default: throw new CommandConstructionException("Could not create command for BoatAction: " + action + ". Unknown BoatAction.");
}
}
}
diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java
index ff09103d..74c5e95b 100644
--- a/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java
+++ b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java
@@ -1,25 +1,24 @@
package mock.model.commandFactory;
-import mock.model.MockRace;
-
-import java.util.Stack;
+import java.util.ArrayDeque;
+import java.util.Queue;
/**
* Wraps multiple commands into a composite to execute queued commands during a frame.
*/
public class CompositeCommand implements Command {
- private Stack commands;
+ private Queue commands;
public CompositeCommand() {
- this.commands = new Stack<>();
+ this.commands = new ArrayDeque<>();
}
public void addCommand(Command command) {
- commands.push(command);
+ commands.add(command);
}
@Override
- public void execute(MockRace race) {
- while(!commands.isEmpty()) commands.pop().execute(race);
+ 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/network/StreamRelated/MessageDeserialiser.java b/racevisionGame/src/main/java/network/StreamRelated/MessageDeserialiser.java
index 39cb0024..028e01a9 100644
--- a/racevisionGame/src/main/java/network/StreamRelated/MessageDeserialiser.java
+++ b/racevisionGame/src/main/java/network/StreamRelated/MessageDeserialiser.java
@@ -3,15 +3,12 @@ package network.StreamRelated;
import network.BinaryMessageDecoder;
import network.Exceptions.InvalidMessageException;
-import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.AC35Data;
import shared.model.RunnableWithFramePeriod;
import java.io.*;
import java.nio.ByteBuffer;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -36,9 +33,11 @@ public class MessageDeserialiser implements RunnableWithFramePeriod {
/**
- * Ack numbers used in messages.
+ * Determines whether or not this runnable is currently running.
*/
- private int ackNumber = 1;
+ private boolean isRunning;
+
+
/**
@@ -52,16 +51,6 @@ public class MessageDeserialiser implements RunnableWithFramePeriod {
}
- /**
- * Increments the ackNumber value, and returns it.
- * @return Incremented ackNumber.
- */
- private int getNextAckNumber(){
- this.ackNumber++;
-
- return this.ackNumber;
- }
-
/**
@@ -119,34 +108,35 @@ public class MessageDeserialiser implements RunnableWithFramePeriod {
}
+ /**
+ * Determines whether or not this runnable is running.
+ * @return True means that it is still running, false means that it has stopped.
+ */
+ public boolean isRunning() {
+ return isRunning;
+ }
+
+
@Override
public void run() {
- long previousFrameTime = System.currentTimeMillis();
+ isRunning = true;
while (!Thread.interrupted()) {
-
- long currentFrameTime = System.currentTimeMillis();
- waitForFramePeriod(previousFrameTime, currentFrameTime, 16);
- previousFrameTime = currentFrameTime;
-
-
//Reads the next message.
try {
AC35Data message = this.getNextMessage();
messagesRead.add(message);
}
- catch (InvalidMessageException | IOException e) {
-
- Logger.getGlobal().log(Level.WARNING, "Unable to read message.", e);
+ catch (InvalidMessageException e) {
+ Logger.getGlobal().log(Level.WARNING, "Unable to read message on thread: " + Thread.currentThread() + ".", e);
- try {
- inputStream.reset();
- } catch (IOException e1) {
- Logger.getGlobal().log(Level.WARNING, "Unable to reset inputStream.", e);
- }
+ } catch (IOException e) {
+ Logger.getGlobal().log(Level.SEVERE, "Unable to read inputStream: " + inputStream + " on thread: " + Thread.currentThread() + ".", e);
+ isRunning = false;
+ return;
}
diff --git a/racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java b/racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java
index 02e6f7a6..8d2a7038 100644
--- a/racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java
+++ b/racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java
@@ -81,7 +81,7 @@ public class MessageSerialiser implements RunnableWithFramePeriod {
isRunning = true;
- while (isRunning) {
+ while (!Thread.interrupted()) {
long currentFrameTime = System.currentTimeMillis();
@@ -104,8 +104,9 @@ public class MessageSerialiser implements RunnableWithFramePeriod {
Logger.getGlobal().log(Level.WARNING, "Could not encode message: " + message, e);
} catch (IOException e) {
- Logger.getGlobal().log(Level.WARNING, "Could not write message to outputStream: " + outputStream, e);
+ Logger.getGlobal().log(Level.SEVERE, "Could not write message to outputStream: " + outputStream + " on thread: " + Thread.currentThread(), e);
isRunning = false;
+ return;
}
}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java
index 5f1e2d8d..a8caad09 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java
@@ -19,6 +19,7 @@ import java.net.URL;
import java.net.UnknownHostException;
import java.util.ResourceBundle;
+//TODO it appears this view/controller was replaced by Lobby.fxml. Remove?
/**
* Controls the connection that the VIsualiser can connect to.
*/
@@ -82,11 +83,6 @@ public class ConnectionController extends Controller {
private ObservableList connections;
- /**
- * Represents whether the client is currently hosting a game already - this is to ensure they don't launch multiple servers.
- */
- private boolean currentlyHostingGame = false;
-
@Override
public void initialize(URL location, ResourceBundle resources) {
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java
index 7873e8e6..3360e514 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java
@@ -47,7 +47,7 @@ public class HostController extends Controller {
*/
public void hostGamePressed() throws IOException{
try {
- Event game = new Event();
+ Event game = new Event(true);
game.start();
connectSocket("localhost", 4942);
} catch (EventConstructionException e) {
@@ -78,7 +78,6 @@ public class HostController extends Controller {
*/
public void hostGame(){
hostWrapper.setVisible(true);
- System.out.println("Reacted hostGame");
}
}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java
index dc4d8e59..5ae51d35 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java
@@ -58,7 +58,6 @@ public class LobbyController extends Controller {
}
else {
joinGameBtn.setDisable(true);
- System.out.println(curr.statusProperty().getValue());
}
});
joinGameBtn.setDisable(true);
@@ -70,11 +69,6 @@ public class LobbyController extends Controller {
public void refreshBtnPressed(){
for(RaceConnection connection: connections) {
connection.check();
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
}
try {
if (lobbyTable.getSelectionModel().getSelectedItem().statusProperty().getValue().equals("Ready")) {
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java
index 80ef2da4..900e43b0 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java
@@ -5,6 +5,7 @@ import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
import visualiser.app.VisualiserInput;
import visualiser.gameController.ControllerClient;
+import visualiser.model.ServerConnection;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRace;
@@ -36,12 +37,12 @@ public class MainController extends Controller {
/**
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
- * @param visualiserInput The object used to read packets from the race server.
* @param visualiserRace The object modelling the race.
* @param controllerClient Socket Client that manipulates the controller.
+ * @param serverConnection The connection to the server.
*/
- public void beginRace(VisualiserInput visualiserInput, VisualiserRace visualiserRace, ControllerClient controllerClient) {
- raceController.startRace(visualiserInput, visualiserRace, controllerClient);
+ public void beginRace(VisualiserRace visualiserRace, ControllerClient controllerClient, ServerConnection serverConnection) {
+ raceController.startRace(visualiserRace, controllerClient, serverConnection);
}
/**
@@ -103,10 +104,15 @@ public class MainController extends Controller {
AnchorPane.setLeftAnchor(startController.startWrapper(), 0.0);
AnchorPane.setRightAnchor(startController.startWrapper(), 0.0);
- AnchorPane.setTopAnchor(connectionController.startWrapper(), 0.0);
- AnchorPane.setBottomAnchor(connectionController.startWrapper(), 0.0);
- AnchorPane.setLeftAnchor(connectionController.startWrapper(), 0.0);
- AnchorPane.setRightAnchor(connectionController.startWrapper(), 0.0);
+ AnchorPane.setTopAnchor(lobbyController.startWrapper(), 0.0);
+ AnchorPane.setBottomAnchor(lobbyController.startWrapper(), 0.0);
+ AnchorPane.setLeftAnchor(lobbyController.startWrapper(), 0.0);
+ AnchorPane.setRightAnchor(lobbyController.startWrapper(), 0.0);
+
+ AnchorPane.setTopAnchor(hostController.startWrapper(), 0.0);
+ AnchorPane.setBottomAnchor(hostController.startWrapper(), 0.0);
+ AnchorPane.setLeftAnchor(hostController.startWrapper(), 0.0);
+ AnchorPane.setRightAnchor(hostController.startWrapper(), 0.0);
AnchorPane.setTopAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setBottomAnchor(finishController.finishWrapper, 0.0);
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java
index 5a1836f6..0a5fb33a 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java
@@ -34,10 +34,6 @@ import java.util.logging.Logger;
*/
public class RaceController extends Controller {
- /**
- * The object used to read packets from the connected server.
- */
- private VisualiserInput visualiserInput;
/**
* The race object which describes the currently occurring race.
@@ -69,6 +65,11 @@ public class RaceController extends Controller {
*/
private ControllerClient controllerClient;
+ /**
+ * The connection to the server.
+ */
+ private ServerConnection serverConnection;
+
@FXML private GridPane canvasBase;
@@ -351,15 +352,15 @@ public class RaceController extends Controller {
/**
* Displays a specified race.
- * @param visualiserInput Object used to read packets from server.
* @param visualiserRace Object modelling the race.
* @param controllerClient Socket Client that manipulates the controller.
+ * @param serverConnection The connection to the server.
*/
- public void startRace(VisualiserInput visualiserInput, VisualiserRace visualiserRace, ControllerClient controllerClient) {
+ public void startRace(VisualiserRace visualiserRace, ControllerClient controllerClient, ServerConnection serverConnection) {
- this.visualiserInput = visualiserInput;
this.visualiserRace = visualiserRace;
this.controllerClient = controllerClient;
+ this.serverConnection = serverConnection;
initialiseRace();
@@ -420,6 +421,15 @@ public class RaceController extends Controller {
}
+ //Return to main screen if we lose connection.
+ if (!serverConnection.isAlive()) {
+ race.setVisible(false);
+ parent.enterTitle();
+ //TODO currently this doesn't work correctly - the title screen remains visible after clicking join game
+ //TODO we should display an error to the user
+ //TODO also need to "reset" any state (race, connections, etc...).
+ }
+
}
}.start();
}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java
index 2f13aae2..82ac39f6 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java
@@ -74,6 +74,11 @@ public class StartController extends Controller implements Observer {
*/
private ServerConnection serverConnection;
+ /**
+ * Writes BoatActions to outgoing message queue.
+ */
+ private ControllerClient controllerClient;
+
/**
* The race object which describes the currently occurring race.
@@ -85,7 +90,7 @@ public class StartController extends Controller implements Observer {
/**
* An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor.
*/
- List colors = new ArrayList<>(Arrays.asList(
+ private List colors = new ArrayList<>(Arrays.asList(
Color.BLUEVIOLET,
Color.BLACK,
Color.RED,
@@ -130,6 +135,14 @@ public class StartController extends Controller implements Observer {
this.visualiserRace = new VisualiserRace(boatDataSource, raceDataSource, regattaDataSource, latestMessages, this.colors);
new Thread(this.visualiserRace).start();
+ //Ugly. TODO refactor
+ ObservableList boats = visualiserRace.getBoats();
+ for (VisualiserBoat boat : boats) {
+ if (boat.getSourceID() == serverConnection.getAllocatedSourceID()) {
+ boat.setClientBoat(true);
+ }
+ }
+
//Initialise the boat table.
initialiseBoatTable(this.visualiserRace);
@@ -264,7 +277,7 @@ public class StartController extends Controller implements Observer {
startWrapper.setVisible(false);
start.setVisible(false);
- parent.beginRace(visualiserInput, visualiserRace, controllerClient);
+ parent.beginRace(visualiserRace, controllerClient, serverConnection);
}
}
}.start();
@@ -315,6 +328,7 @@ public class StartController extends Controller implements Observer {
LatestMessages latestMessages = new LatestMessages();
this.serverConnection = new ServerConnection(socket, latestMessages);
+ this.controllerClient = serverConnection.getControllerClient();
//Store a reference to latestMessages so that we can observe it.
diff --git a/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java
index 8ad14a1b..0579b623 100644
--- a/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java
+++ b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java
@@ -1,17 +1,11 @@
package visualiser.app;
-import network.BinaryMessageDecoder;
-import network.Exceptions.InvalidMessageException;
import network.Messages.*;
import shared.model.RunnableWithFramePeriod;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.net.Socket;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
-import static network.Utils.ByteConverter.bytesToShort;
/**
* TCP client which receives packets/messages from a race data source
@@ -29,7 +23,6 @@ public class VisualiserInput implements RunnableWithFramePeriod {
private long lastHeartbeatSequenceNum = -1;
-
/**
* Incoming messages from server.
*/
@@ -45,7 +38,8 @@ public class VisualiserInput implements RunnableWithFramePeriod {
/**
* Constructs a visualiserInput to convert an incoming stream of messages into LatestMessages.
- * @param latestMessages Object to place messages in.
+ *
+ * @param latestMessages Object to place messages in.
* @param incomingMessages The incoming queue of messages.
*/
public VisualiserInput(LatestMessages latestMessages, BlockingQueue incomingMessages) {
@@ -58,6 +52,7 @@ public class VisualiserInput implements RunnableWithFramePeriod {
/**
* Returns the LatestMessages object, which can be queried for any received race related messages.
+ *
* @return The LatestMessages object.
*/
public LatestMessages getLatestMessages() {
@@ -66,6 +61,7 @@ public class VisualiserInput implements RunnableWithFramePeriod {
/**
* Calculates the time since last heartbeat, in milliseconds.
+ *
* @return Time since last heartbeat, in milliseconds..
*/
private double timeSinceHeartbeat() {
@@ -74,22 +70,176 @@ public class VisualiserInput implements RunnableWithFramePeriod {
}
-
-
@Override
public void run() {
- //Handshake.
+ while (!Thread.interrupted()) {
- //Main loop.
- // take message
- // create command
- // place in command queue
+ AC35Data message = null;
+ try {
+ message = incomingMessages.take();
+ } catch (InterruptedException e) {
+ Logger.getGlobal().log(Level.SEVERE, "VisualiserInput was interrupted on thread: " + Thread.currentThread() + " while waiting for messages.");
+ Thread.currentThread().interrupt();
+ return;
+ }
+ //TODO refactor below
+ //Checks which message is being received and does what is needed for that message.
+ switch (message.getType()) {
+ //Heartbeat.
+ case HEARTBEAT: {
+ HeartBeat heartBeat = (HeartBeat) message;
- }
+ //Check that the heartbeat number is greater than the previous value, and then set the last heartbeat time.
+ if (heartBeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) {
+ lastHeartbeatTime = System.currentTimeMillis();
+ lastHeartbeatSequenceNum = heartBeat.getSequenceNumber();
+ //System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum);
+ }
+
+ break;
+ }
+
+ //RaceStatus.
+ case RACESTATUS: {
+ RaceStatus raceStatus = (RaceStatus) message;
+
+ //System.out.println("Race Status Message");
+ this.latestMessages.setRaceStatus(raceStatus);
+
+ for (BoatStatus boatStatus : raceStatus.getBoatStatuses()) {
+ this.latestMessages.setBoatStatus(boatStatus);
+ }
+
+ break;
+ }
+
+ //DisplayTextMessage.
+ case DISPLAYTEXTMESSAGE: {
+ //System.out.println("Display Text Message");
+ //No decoder for this.
+
+ break;
+ }
+
+ //XMLMessage.
+ case XMLMESSAGE: {
+ XMLMessage xmlMessage = (XMLMessage) message;
+
+ //System.out.println("XML Message!");
+
+ this.latestMessages.setXMLMessage(xmlMessage);
+
+ break;
+ }
+
+ //RaceStartStatus.
+ case RACESTARTSTATUS: {
+
+ //System.out.println("Race Start Status Message");
+
+ break;
+ }
+
+ //YachtEventCode.
+ case YACHTEVENTCODE: {
+ //YachtEventCode yachtEventCode = (YachtEventCode) message;
+
+ //System.out.println("Yacht Event Code!");
+ //No decoder for this.
+
+ break;
+ }
+
+ //YachtActionCode.
+ case YACHTACTIONCODE: {
+ //YachtActionCode yachtActionCode = (YachtActionCode) message;
+
+ //System.out.println("Yacht Action Code!");
+ // No decoder for this.
+
+ break;
+ }
+ //ChatterText.
+ case CHATTERTEXT: {
+ //ChatterText chatterText = (ChatterText) message;
+
+ //System.out.println("Chatter Text Message!");
+ //No decoder for this.
+
+ break;
+ }
+
+ //BoatLocation.
+ case BOATLOCATION: {
+ BoatLocation boatLocation = (BoatLocation) message;
+
+ //System.out.println("Boat Location!");
+
+ BoatLocation existingBoatLocation = this.latestMessages.getBoatLocationMap().get(boatLocation.getSourceID());
+ if (existingBoatLocation != null) {
+ //If our boatlocation map already contains a boat location message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
+ if (boatLocation.getTime() > existingBoatLocation.getTime()) {
+ //If it is, replace the old message.
+ this.latestMessages.setBoatLocation(boatLocation);
+ }
+ } else {
+ //If the map _doesn't_ already contain a message for this boat, insert the message.
+ this.latestMessages.setBoatLocation(boatLocation);
+ }
+
+ break;
+ }
+
+ //MarkRounding.
+ case MARKROUNDING: {
+ MarkRounding markRounding = (MarkRounding) message;
+
+ //System.out.println("Mark Rounding Message!");
+
+ MarkRounding existingMarkRounding = this.latestMessages.getMarkRoundingMap().get(markRounding.getSourceID());
+ if (existingMarkRounding != null) {
+
+ //If our markRoundingMap already contains a mark rounding message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
+ if (markRounding.getTime() > existingMarkRounding.getTime()) {
+ //If it is, replace the old message.
+ this.latestMessages.setMarkRounding(markRounding);
+ }
+
+ } else {
+ //If the map _doesn't_ already contain a message for this boat, insert the message.
+ this.latestMessages.setMarkRounding(markRounding);
+ }
+
+ break;
+ }
+
+ //CourseWinds.
+ case COURSEWIND: {
+
+ //System.out.println("Course Wind Message!");
+ CourseWinds courseWinds = (CourseWinds) message;
+
+ this.latestMessages.setCourseWinds(courseWinds);
+
+ break;
+ }
+
+
+ }
+
+ //Main loop.
+ // take message
+ // create command
+ // place in command queue
+
+
+ }
+
+ }
}
diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java
index 3757dc01..b9dde4af 100644
--- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java
+++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java
@@ -1,18 +1,15 @@
package visualiser.gameController;
+import mock.exceptions.CommandConstructionException;
+import mock.model.MockRace;
import mock.model.commandFactory.Command;
import mock.model.commandFactory.CommandFactory;
import mock.model.commandFactory.CompositeCommand;
-import network.BinaryMessageDecoder;
-import network.Exceptions.InvalidMessageException;
-import network.MessageDecoders.BoatActionDecoder;
import network.Messages.AC35Data;
import network.Messages.BoatAction;
import network.Messages.Enums.MessageType;
+import shared.model.RunnableWithFramePeriod;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.io.InputStream;
import java.util.concurrent.BlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -20,7 +17,7 @@ import java.util.logging.Logger;
/**
* Service for dispatching key press data to race from client
*/
-public class ControllerServer implements Runnable {
+public class ControllerServer implements RunnableWithFramePeriod {
/**
@@ -33,6 +30,11 @@ public class ControllerServer implements Runnable {
* Collection of commands from client for race to execute.
*/
private CompositeCommand compositeCommand;
+
+ /**
+ * The context for each command.
+ */
+ private MockRace raceState;
/**
* This is the source ID associated with the client.
@@ -46,15 +48,18 @@ public class ControllerServer implements Runnable {
* @param compositeCommand Commands for the race to execute.
* @param inputQueue The queue of messages to read from.
* @param clientSourceID The source ID of the client's boat.
+ * @param raceState The context for each command.
*/
- public ControllerServer(CompositeCommand compositeCommand, BlockingQueue inputQueue, int clientSourceID) {
+ public ControllerServer(CompositeCommand compositeCommand, BlockingQueue inputQueue, int clientSourceID, MockRace raceState) {
this.compositeCommand = compositeCommand;
this.inputQueue = inputQueue;
this.clientSourceID = clientSourceID;
+ this.raceState = raceState;
}
+
/**
* Wait for controller key input from client and loop.
*/
@@ -70,10 +75,17 @@ public class ControllerServer implements Runnable {
BoatAction boatAction = (BoatAction) message;
+
boatAction.setSourceID(clientSourceID);
- Command command = CommandFactory.createCommand(boatAction);
- compositeCommand.addCommand(command);
+ try {
+ Command command = CommandFactory.createCommand(raceState, boatAction);
+ compositeCommand.addCommand(command);
+
+ } catch (CommandConstructionException e) {
+ Logger.getGlobal().log(Level.WARNING, "ControllerServer could not create a Command for BoatAction: " + boatAction + ".", e);
+
+ }
}
@@ -81,6 +93,7 @@ public class ControllerServer implements Runnable {
Logger.getGlobal().log(Level.WARNING, "ControllerServer Interrupted while waiting for message on incoming message queue.", e);
Thread.currentThread().interrupt();
return;
+
}
}
diff --git a/racevisionGame/src/main/java/visualiser/model/Ping.java b/racevisionGame/src/main/java/visualiser/model/Ping.java
index b81a3e0c..b31716b5 100644
--- a/racevisionGame/src/main/java/visualiser/model/Ping.java
+++ b/racevisionGame/src/main/java/visualiser/model/Ping.java
@@ -22,16 +22,14 @@ public class Ping implements Runnable {
this.rc = rc;
}
- public boolean pingPort(){
- //TODO the connection needs to be moved to its own thread, so it doesn't block fx thread.
+ public boolean pingPort() {
InetSocketAddress i = new InetSocketAddress(hostname, port);
try (Socket s = new Socket()){
- s.connect(i, 750);//TODO this should be at least a second or two, once moved to its own thread
+ s.connect(i, 1500);
s.shutdownInput();
s.shutdownOutput();
s.close();
rc.statusProperty().set("Ready");
- //System.out.println(String.valueOf(s.isClosed()));
return true;
} catch (IOException e) {
rc.statusProperty().set("Offline");
diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java
index e9b4c31c..16393228 100644
--- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java
+++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java
@@ -314,6 +314,10 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//The position may be null if we haven't received any BoatLocation messages yet.
if (boat.getCurrentPosition() != null) {
+ if (boat.isClientBoat()) {
+ drawClientBoat(boat);
+ }
+
//Convert position to graph coordinate.
GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition());
@@ -343,6 +347,39 @@ public class ResizableRaceCanvas extends ResizableCanvas {
}
+ /**
+ * Draws extra decorations to show which boat has been assigned to the client.
+ * @param boat The client's boat.
+ */
+ private void drawClientBoat(VisualiserBoat boat) {
+
+ //Convert position to graph coordinate.
+ GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition());
+
+ //The x coordinates of each vertex of the boat.
+ double[] x = {
+ pos.getX() - 12,
+ pos.getX(),
+ pos.getX() + 12 };
+
+ //The y coordinates of each vertex of the boat.
+ double[] y = {
+ pos.getY() + 24,
+ pos.getY() - 24,
+ pos.getY() + 24 };
+
+ //The above shape is essentially a triangle 24px wide, and 48 long.
+
+ //Draw the boat.
+ gc.setFill(Color.BLACK);
+
+ gc.save();
+ rotate(boat.getBearing().degrees(), pos.getX(), pos.getY());
+ gc.fillPolygon(x, y, 3);
+ gc.restore();
+
+ }
+
/**
* Draws the wake for a given boat.
diff --git a/racevisionGame/src/main/java/visualiser/model/ServerConnection.java b/racevisionGame/src/main/java/visualiser/model/ServerConnection.java
index ececdd4c..742ab968 100644
--- a/racevisionGame/src/main/java/visualiser/model/ServerConnection.java
+++ b/racevisionGame/src/main/java/visualiser/model/ServerConnection.java
@@ -1,12 +1,7 @@
package visualiser.model;
-import mock.app.MockOutput;
import mock.enums.ConnectionStateEnum;
-import mock.exceptions.SourceIDAllocationException;
-import mock.model.HeartBeatService;
-import mock.model.SourceIdAllocator;
-import mock.model.commandFactory.CompositeCommand;
import network.Messages.AC35Data;
import network.Messages.Enums.JoinAcceptanceEnum;
import network.Messages.Enums.MessageType;
@@ -19,7 +14,6 @@ import network.StreamRelated.MessageSerialiser;
import shared.exceptions.HandshakeException;
import visualiser.app.VisualiserInput;
import visualiser.gameController.ControllerClient;
-import visualiser.gameController.ControllerServer;
import java.io.IOException;
import java.net.Socket;
@@ -38,6 +32,11 @@ public class ServerConnection implements Runnable {
*/
private Socket socket;
+ /**
+ * The source ID that has been allocated to the client.
+ */
+ private int allocatedSourceID = 0;
+
/**
* Latest snapshot of the race, received from the server.
@@ -106,6 +105,9 @@ public class ServerConnection implements Runnable {
new Thread(messageSerialiser, "ServerConnection()->MessageSerialiser thread " + messageSerialiser).start();
new Thread(messageDeserialiser, "ServerConnection()->MessageDeserialiser thread " + messageDeserialiser).start();
+
+ this.controllerClient = new ControllerClient(outputQueue);
+
}
@@ -135,25 +137,23 @@ public class ServerConnection implements Runnable {
connectionState = ConnectionStateEnum.WAITING_FOR_HANDSHAKE;
- sendJoinAcceptanceMessage(RequestToJoinEnum.PARTICIPANT);
+ sendRequestToJoinMessage(RequestToJoinEnum.PARTICIPANT);
JoinAcceptance joinAcceptance = waitForJoinAcceptance();
- int allocatedSourceID = 0;
//If we join successfully...
if (joinAcceptance.getAcceptanceType() == JoinAcceptanceEnum.JOIN_SUCCESSFUL) {
- allocatedSourceID = joinAcceptance.getSourceID();
- //TODO need to do something with the ID - maybe flag the correct visualiser boat as being the client's boat?
+ this.allocatedSourceID = joinAcceptance.getSourceID();
+
- this.controllerClient = new ControllerClient(inputQueue);
//new Thread(controllerClient, "ServerConnection.run()->ControllerClient thread " + controllerClient).start();
}
- this.visualiserInput = new VisualiserInput(latestMessages, outputQueue);
+ this.visualiserInput = new VisualiserInput(latestMessages, inputQueue);
new Thread(visualiserInput, "ServerConnection.run()->VisualiserInput thread " + visualiserInput).start();
@@ -200,7 +200,7 @@ public class ServerConnection implements Runnable {
* @param requestType The type of request to send
* @throws HandshakeException Thrown if the thread is interrupted while placing message on the outgoing message queue.
*/
- private void sendJoinAcceptanceMessage(RequestToJoinEnum requestType) throws HandshakeException {
+ private void sendRequestToJoinMessage(RequestToJoinEnum requestType) throws HandshakeException {
//Send them the source ID.
RequestToJoin requestToJoin = new RequestToJoin(requestType);
@@ -217,12 +217,27 @@ public class ServerConnection implements Runnable {
/**
* Determines whether or not this connection is still alive.
- * This is based off whether the {@link MessageSerialiser} is still alive.
+ * This is based off whether the {@link MessageDeserialiser} is still alive.
* @return True if it is alive, false otherwise.
*/
public boolean isAlive() {
- return messageSerialiser.isRunning();
+ return messageDeserialiser.isRunning();
}
+ /**
+ * Returns the controller client, which writes BoatAction messages to the outgoing queue.
+ * @return The ControllerClient.
+ */
+ public ControllerClient getControllerClient() {
+ return controllerClient;
+ }
+
+ /**
+ * Returns the source ID that has been allocated to the client.
+ * @return Source ID allocated to the client. 0 if it hasn't been allocated.
+ */
+ public int getAllocatedSourceID() {
+ return allocatedSourceID;
+ }
}
diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java
index 57ba0790..c99c85cb 100644
--- a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java
+++ b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java
@@ -57,6 +57,11 @@ public class VisualiserBoat extends Boat {
*/
private static final double wakeScale = 5;
+ /**
+ * If true then this boat has been allocated to the client.
+ */
+ private boolean isClientBoat = false;
+
@@ -214,4 +219,21 @@ public class VisualiserBoat extends Boat {
return " -";
}
}
+
+
+ /**
+ * Returns whether or not this boat has been assigned to the client.
+ * @return True if this is the client's boat.
+ */
+ public boolean isClientBoat() {
+ return isClientBoat;
+ }
+
+ /**
+ * Sets whether or not this boat has been assigned to the client.
+ * @param clientBoat True if this is the client's boat.
+ */
+ public void setClientBoat(boolean clientBoat) {
+ isClientBoat = clientBoat;
+ }
}
diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java
index 15067515..0dc172cd 100644
--- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java
+++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java
@@ -347,13 +347,16 @@ public class VisualiserRace extends Race implements Runnable {
@Override
public void handle(long arg0) {
+
//Calculate the frame period.
long currentFrameTime = System.currentTimeMillis();
long framePeriod = currentFrameTime - lastFrameTime;
//Update race status.
- updateRaceStatus(latestMessages.getRaceStatus());
+ if (latestMessages.getRaceStatus() != null) {
+ updateRaceStatus(latestMessages.getRaceStatus());
+ }
//Update racing boats.
diff --git a/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml b/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml
index b43abe7f..0b2b6a00 100644
--- a/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml
+++ b/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml
@@ -33,8 +33,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/racevisionGame/src/main/resources/mock/mockXML/boatsSinglePlayer.xml b/racevisionGame/src/main/resources/mock/mockXML/boatsSinglePlayer.xml
index 98058f3d..e783821f 100644
--- a/racevisionGame/src/main/resources/mock/mockXML/boatsSinglePlayer.xml
+++ b/racevisionGame/src/main/resources/mock/mockXML/boatsSinglePlayer.xml
@@ -36,20 +36,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml b/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml
new file mode 100644
index 00000000..a5d6761f
--- /dev/null
+++ b/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml
@@ -0,0 +1,52 @@
+
+
+ 5326
+ FLEET
+ CREATION_TIME
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml b/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml
index 83e36f85..51199001 100644
--- a/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml
+++ b/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml
@@ -5,7 +5,12 @@
CREATION_TIME
+
+
+
+
+
@@ -49,4 +54,4 @@
-
\ No newline at end of file
+
diff --git a/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml b/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml
index f620bfeb..4308a47f 100644
--- a/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml
+++ b/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml
@@ -1,13 +1,18 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/racevisionGame/src/main/resources/visualiser/scenes/lobby.fxml b/racevisionGame/src/main/resources/visualiser/scenes/lobby.fxml
index 13159bec..2f84063e 100644
--- a/racevisionGame/src/main/resources/visualiser/scenes/lobby.fxml
+++ b/racevisionGame/src/main/resources/visualiser/scenes/lobby.fxml
@@ -1,13 +1,18 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
@@ -35,31 +40,52 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/racevisionGame/src/test/java/mock/dataInput/PolarParserTest.java b/racevisionGame/src/test/java/mock/dataInput/PolarParserTest.java
index 78d3364f..b051ba79 100644
--- a/racevisionGame/src/test/java/mock/dataInput/PolarParserTest.java
+++ b/racevisionGame/src/test/java/mock/dataInput/PolarParserTest.java
@@ -14,6 +14,13 @@ import static org.junit.Assert.fail;
public class PolarParserTest {
+
+ public static Polars createPolars() {
+ Polars polars = PolarParser.parse("mock/polars/acc_polars.csv");
+ return polars;
+ }
+
+
/**
* Tests if we can parse a valid polar data file (stored in a string), and create a polar table.
*/
diff --git a/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java b/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java
new file mode 100644
index 00000000..6f67dc30
--- /dev/null
+++ b/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java
@@ -0,0 +1,72 @@
+package mock.model;
+
+import org.junit.Before;
+import org.junit.Test;
+import shared.model.Bearing;
+import shared.model.Wind;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests the {@link ConstantWindGenerator}.
+ */
+public class ConstantWindGeneratorTest {
+
+ WindGenerator windGenerator;
+ Bearing windBearing;
+ double windSpeedKnots;
+
+ @Before
+ public void setUp() throws Exception {
+
+ windBearing = Bearing.fromDegrees(78.5);
+ windSpeedKnots = 18.54;
+
+ windGenerator = new ConstantWindGenerator(windBearing, windSpeedKnots);
+
+ }
+
+
+ /**
+ * Tests if the {@link WindGenerator#generateBaselineWind()} function works.
+ */
+ @Test
+ public void generateBaselineTest() {
+
+ int repetitions = 100;
+
+ for (int i = 0; i < repetitions; i++) {
+
+ Wind wind = windGenerator.generateBaselineWind();
+
+ assertEquals(windBearing.degrees(), wind.getWindDirection().degrees(), 0.01);
+ assertEquals(windSpeedKnots, wind.getWindSpeed(), 0.01);
+
+ }
+
+ }
+
+
+ /**
+ * Tests if the {@link WindGenerator#generateNextWind(Wind)} ()} function works.
+ */
+ @Test
+ public void generateNextWindTest() {
+
+ int repetitions = 100;
+
+ Wind wind = windGenerator.generateBaselineWind();
+
+ for (int i = 0; i < repetitions; i++) {
+
+ wind = windGenerator.generateNextWind(wind);
+
+ assertEquals(windBearing.degrees(), wind.getWindDirection().degrees(), 0.01);
+ assertEquals(windSpeedKnots, wind.getWindSpeed(), 0.01);
+
+ }
+
+ }
+
+}
diff --git a/racevisionGame/src/test/java/mock/model/MockRaceTest.java b/racevisionGame/src/test/java/mock/model/MockRaceTest.java
index cf530665..3c01b321 100644
--- a/racevisionGame/src/test/java/mock/model/MockRaceTest.java
+++ b/racevisionGame/src/test/java/mock/model/MockRaceTest.java
@@ -1,7 +1,44 @@
package mock.model;
+import mock.dataInput.PolarParserTest;
+import network.Messages.LatestMessages;
+import shared.dataInput.*;
+import shared.exceptions.InvalidBoatDataException;
+import shared.exceptions.InvalidRaceDataException;
+import shared.exceptions.InvalidRegattaDataException;
+import shared.model.Bearing;
+import shared.model.Constants;
+
import static org.junit.Assert.*;
public class MockRaceTest {
//TODO
+
+
+ /**
+ * Creates a MockRace for use in testing.
+ * Has a constant wind generator.
+ * @return MockRace for testing.
+ * @throws InvalidBoatDataException
+ * @throws InvalidRaceDataException
+ * @throws InvalidRegattaDataException
+ */
+ public static MockRace createMockRace() throws InvalidBoatDataException, InvalidRaceDataException, InvalidRegattaDataException {
+
+ BoatDataSource boatDataSource = BoatXMLReaderTest.createBoatDataSource();
+ RaceDataSource raceDataSource = RaceXMLReaderTest.createRaceDataSource();
+ RegattaDataSource regattaDataSource = RegattaXMLReaderTest.createRegattaDataSource();
+
+ LatestMessages latestMessages = new LatestMessages();
+
+ Polars polars = PolarParserTest.createPolars();
+
+ WindGenerator windGenerator = new ConstantWindGenerator(Bearing.fromDegrees(230), 10);
+
+ MockRace mockRace = new MockRace(boatDataSource, raceDataSource, regattaDataSource, latestMessages, polars, Constants.RaceTimeScale, windGenerator);
+
+
+ return mockRace;
+
+ }
}
diff --git a/racevisionGame/src/test/java/mock/model/WindGeneratorTest.java b/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java
similarity index 86%
rename from racevisionGame/src/test/java/mock/model/WindGeneratorTest.java
rename to racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java
index 74ad7421..76eed977 100644
--- a/racevisionGame/src/test/java/mock/model/WindGeneratorTest.java
+++ b/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java
@@ -8,10 +8,10 @@ import shared.model.Wind;
import static org.junit.Assert.*;
-public class WindGeneratorTest {
+public class RandomWindGeneratorTest {
- private WindGenerator windGenerator;
+ private RandomWindGenerator randomWindGenerator;
private Bearing windBaselineBearing;
private Bearing windBearingLowerBound;
@@ -36,7 +36,7 @@ public class WindGeneratorTest {
this.windSpeedLowerBound = 7;
this.windSpeedUpperBound = 20;
- this.windGenerator = new WindGenerator(
+ this.randomWindGenerator = new RandomWindGenerator(
windBaselineBearing,
windBearingLowerBound,
windBearingUpperBound,
@@ -55,7 +55,7 @@ public class WindGeneratorTest {
@Test
public void generateBaselineWindTest() {
- Wind wind = windGenerator.generateBaselineWind();
+ Wind wind = randomWindGenerator.generateBaselineWind();
assertEquals(windBaselineSpeed, wind.getWindSpeed(), speedKnotsEpsilon);
assertEquals(windBaselineBearing.degrees(), wind.getWindDirection().degrees(), bearingDegreeEpsilon);
@@ -72,7 +72,7 @@ public class WindGeneratorTest {
for (int i = 0; i < randomWindCount; i++) {
- Wind wind = windGenerator.generateRandomWind();
+ Wind wind = randomWindGenerator.generateRandomWind();
assertTrue(wind.getWindSpeed() >= windSpeedLowerBound);
assertTrue(wind.getWindSpeed() <= windSpeedUpperBound);
@@ -91,13 +91,13 @@ public class WindGeneratorTest {
@Test
public void generateNextWindTest() {
- Wind wind = windGenerator.generateBaselineWind();
+ Wind wind = randomWindGenerator.generateBaselineWind();
int randomWindCount = 1000;
for (int i = 0; i < randomWindCount; i++) {
- wind = windGenerator.generateNextWind(wind);
+ wind = randomWindGenerator.generateNextWind(wind);
assertTrue(wind.getWindSpeed() >= windSpeedLowerBound);
assertTrue(wind.getWindSpeed() <= windSpeedUpperBound);
diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java
index c3d0df04..3a48c096 100644
--- a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java
+++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java
@@ -1,31 +1,72 @@
package mock.model.commandFactory;
+import mock.exceptions.CommandConstructionException;
+import mock.model.MockBoat;
import mock.model.MockRace;
+import mock.model.MockRaceTest;
+import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mock;
+import shared.exceptions.InvalidBoatDataException;
+import shared.exceptions.InvalidRaceDataException;
+import shared.exceptions.InvalidRegattaDataException;
+import shared.model.Bearing;
import shared.model.Boat;
import shared.model.Race;
import visualiser.model.VisualiserRace;
+import static org.mockito.Mockito.when;
import static org.testng.Assert.*;
+import static org.mockito.Mockito.mock;
/**
* Created by connortaylorbrown on 4/08/17.
*/
public class WindCommandTest {
- private Race race;
- private Boat boat;
+ private MockRace race;
+ private MockBoat boat;
private Command upwind;
private Command downwind;
+ private double initial;
+
+ private double offset = 3.0;
@Before
- public void setUp() {
- boat = new Boat(0, "Bob", "NZ");
+ public void setUp() throws CommandConstructionException, InvalidBoatDataException, InvalidRegattaDataException, InvalidRaceDataException {
+ race = MockRaceTest.createMockRace();
+
+ boat = race.getBoats().get(0);
+
+
+ //when(race.getWindDirection()).thenReturn(Bearing.fromDegrees(0.0));
+ boat.setBearing(Bearing.fromDegrees(45.0));
+
+ BoatAction upwindAction = new BoatAction(BoatActionEnum.UPWIND);
+ upwindAction.setSourceID(boat.getSourceID());
+
+ BoatAction downwindAction = new BoatAction(BoatActionEnum.DOWNWIND);
+ downwindAction.setSourceID(boat.getSourceID());
+
+ upwind = CommandFactory.createCommand(race, upwindAction);
+ downwind = CommandFactory.createCommand(race, downwindAction);
+
+ 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
+}
diff --git a/racevisionGame/src/test/java/shared/dataInput/BoatXMLReaderTest.java b/racevisionGame/src/test/java/shared/dataInput/BoatXMLReaderTest.java
index 19370be0..60ec2be9 100644
--- a/racevisionGame/src/test/java/shared/dataInput/BoatXMLReaderTest.java
+++ b/racevisionGame/src/test/java/shared/dataInput/BoatXMLReaderTest.java
@@ -31,31 +31,40 @@ public class BoatXMLReaderTest {
private List marks;
- @Before
- public void setUp() {
+ public static BoatDataSource createBoatDataSource() throws InvalidBoatDataException {
String boatXMLString = null;
try {
boatXMLString = XMLReader.readXMLFileToString("mock/mockXML/boatTest.xml", StandardCharsets.UTF_8);
- } catch (Exception e) {
- e.printStackTrace();
- fail("could not read boat xml file into a string");
+ } catch (TransformerException | XMLReaderException e) {
+ throw new InvalidBoatDataException("Could not read boat XML file into a string.", e);
+
}
try {
- boatData = new BoatXMLReader(boatXMLString, XMLFileType.Contents);
+ BoatDataSource boatData = new BoatXMLReader(boatXMLString, XMLFileType.Contents);
+ return boatData;
- boats = new ArrayList<>(boatData.getBoats().values());
- marks = new ArrayList<>(boatData.getMarkerBoats().values());
- } catch (XMLReaderException | InvalidBoatDataException e) {
- e.printStackTrace();
- assertTrue(false);
+ } catch (XMLReaderException e) {
+ throw new InvalidBoatDataException("Could not parse boat XML file.", e);
}
+
+
+ }
+
+ @Before
+ public void setUp() throws InvalidBoatDataException {
+
+ boatData = createBoatDataSource();
+
+ boats = new ArrayList<>(boatData.getBoats().values());
+ marks = new ArrayList<>(boatData.getMarkerBoats().values());
+
}
@Test
diff --git a/racevisionGame/src/test/java/shared/dataInput/RaceXMLReaderTest.java b/racevisionGame/src/test/java/shared/dataInput/RaceXMLReaderTest.java
index 0539a990..6080919d 100644
--- a/racevisionGame/src/test/java/shared/dataInput/RaceXMLReaderTest.java
+++ b/racevisionGame/src/test/java/shared/dataInput/RaceXMLReaderTest.java
@@ -1,5 +1,39 @@
package shared.dataInput;
+import shared.enums.XMLFileType;
+import shared.exceptions.InvalidRaceDataException;
+import shared.exceptions.XMLReaderException;
+
+import javax.xml.transform.TransformerException;
+import java.nio.charset.StandardCharsets;
+
public class RaceXMLReaderTest {
-//TODO
+
+
+
+ public static RaceDataSource createRaceDataSource() throws InvalidRaceDataException {
+
+ String raceXMLString = null;
+
+ try {
+ raceXMLString = XMLReader.readXMLFileToString("mock/mockXML/raceTest.xml", StandardCharsets.UTF_8);
+
+ } catch (TransformerException | XMLReaderException e) {
+ throw new InvalidRaceDataException("Could not read race XML file into a string.", e);
+
+ }
+
+
+ try {
+
+ RaceXMLReader raceData = new RaceXMLReader(raceXMLString, XMLFileType.Contents);
+ return raceData;
+
+
+ } catch (XMLReaderException e) {
+ throw new InvalidRaceDataException("Could not parse race XML file.", e);
+ }
+
+
+ }
}
diff --git a/racevisionGame/src/test/java/shared/dataInput/RegattaXMLReaderTest.java b/racevisionGame/src/test/java/shared/dataInput/RegattaXMLReaderTest.java
index ad9bcd95..2f043c0d 100644
--- a/racevisionGame/src/test/java/shared/dataInput/RegattaXMLReaderTest.java
+++ b/racevisionGame/src/test/java/shared/dataInput/RegattaXMLReaderTest.java
@@ -3,6 +3,11 @@ package shared.dataInput;
import org.junit.Before;
import org.junit.Test;
import shared.enums.XMLFileType;
+import shared.exceptions.InvalidRegattaDataException;
+import shared.exceptions.XMLReaderException;
+
+import javax.xml.transform.TransformerException;
+import java.nio.charset.StandardCharsets;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -14,6 +19,34 @@ import static org.junit.Assert.fail;
public class RegattaXMLReaderTest {
private RegattaXMLReader regatta;
+
+ public static RegattaDataSource createRegattaDataSource() throws InvalidRegattaDataException {
+
+ String regattaXMLString = null;
+
+ try {
+ regattaXMLString = XMLReader.readXMLFileToString("mock/mockXML/regattaTest.xml", StandardCharsets.UTF_8);
+
+ } catch (TransformerException | XMLReaderException e) {
+ throw new InvalidRegattaDataException("Could not read regatta XML file into a string.", e);
+
+ }
+
+
+ try {
+
+ RegattaDataSource raceData = new RegattaXMLReader(regattaXMLString, XMLFileType.Contents);
+ return raceData;
+
+
+ } catch (XMLReaderException e) {
+ throw new InvalidRegattaDataException("Could not parse regatta XML file.", e);
+ }
+
+
+ }
+
+
@Before
public void findFile() {
try {